#! /usr/bin/env python # -*- coding: utf-8 -*-*- # # PyKota : Print Quotas for CUPS # # (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # $Id$ # # """A refunding tool for PyKota""" import sys import os import pwd import time import cStringIO try : from reportlab.pdfgen import canvas from reportlab.lib import pagesizes from reportlab.lib.units import cm except ImportError : hasRL = False else : hasRL = True try : import PIL.Image except ImportError : hasPIL = False else : hasPIL = True import pykota.appinit from pykota.utils import run from pykota.commandline import PyKotaOptionParser, \ checkandset_pagesize, \ checkandset_positiveint from pykota.pdfutils import getPageSize from pykota.errors import PyKotaToolError, PyKotaCommandLineError from pykota.tool import Percent, PyKotaTool class PKRefund(PyKotaTool) : """A class for refund manager.""" validfilterkeys = [ "username", "printername", "hostname", "jobid", "billingcode", "start", "end", ] def printVar(self, label, value, size) : """Outputs a variable onto the PDF canvas. Returns the number of points to substract to current Y coordinate. """ xcenter = (self.pagesize[0] / 2.0) - 1*cm self.canvas.saveState() self.canvas.setFont("Helvetica-Bold", size) self.canvas.setFillColorRGB(0, 0, 0) self.canvas.drawRightString(xcenter, self.ypos, "%s :" % label) self.canvas.setFont("Courier-Bold", size) self.canvas.setFillColorRGB(0, 0, 1) self.canvas.drawString(xcenter + 0.5*cm, self.ypos, value) self.canvas.restoreState() self.ypos -= (size + 4) def pagePDF(self, receiptnumber, name, values, unit, reason) : """Generates a new page in the PDF document.""" if values["nbpages"] : self.canvas.doForm("background") self.ypos = self.yorigine - (cm + 20) self.printVar(_("Refunding receipt"), "#%s" % receiptnumber, 22) self.printVar(_("Username"), name, 22) self.ypos -= 20 datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace") self.printVar(_("Edited on"), datetime, 14) self.ypos -= 20 self.printVar(_("Jobs refunded"), str(values["nbjobs"]), 18) self.printVar(_("Pages refunded"), str(values["nbpages"]), 18) self.printVar(_("Amount refunded"), "%.3f %s" % (values["nbcredits"], unit), 18) self.ypos -= 20 self.printVar(_("Reason"), reason, 14) self.canvas.showPage() return 1 return 0 def initPDF(self, logo) : """Initializes the PDF document.""" self.pdfDocument = cStringIO.StringIO() self.canvas = c = canvas.Canvas(self.pdfDocument, \ pagesize=self.pagesize, \ pageCompression=1) c.setAuthor(self.effectiveUserName) c.setTitle(_("PyKota print job refunding receipts")) c.setSubject(_("Print job refunding receipts generated with PyKota")) self.canvas.beginForm("background") self.canvas.saveState() self.ypos = self.pagesize[1] - (2 * cm) xcenter = self.pagesize[0] / 2.0 if logo : try : imglogo = PIL.Image.open(logo) except IOError : self.printInfo("Unable to open image %s" % logo, "warn") else : (width, height) = imglogo.size multi = float(width) / (8 * cm) width = float(width) / multi height = float(height) / multi self.ypos -= height c.drawImage(logo, xcenter - (width / 2.0), \ self.ypos, \ width, height) self.ypos -= (cm + 20) self.canvas.setFont("Helvetica-Bold", 14) self.canvas.setFillColorRGB(0, 0, 0) msg = _("Here's the receipt for the refunding of your print jobs") self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg) self.yorigine = self.ypos self.canvas.restoreState() self.canvas.endForm() def endPDF(self, fname) : """Flushes the PDF generator.""" self.canvas.save() if fname != "-" : outfile = open(fname, "w") outfile.write(self.pdfDocument.getvalue()) outfile.close() else : sys.stdout.write(self.pdfDocument.getvalue()) sys.stdout.flush() def genReceipts(self, peruser, logo, outfname, firstnumber, reason, unit) : """Generates the receipts file.""" if len(peruser) : percent = Percent(self, size=len(peruser)) if outfname != "-" : percent.display("%s...\n" % _("Generating receipts")) self.initPDF(logo) number = firstnumber for (name, values) in peruser.items() : number += self.pagePDF(number, name, values, unit, reason) if outfname != "-" : percent.oneMore() if number > firstnumber : self.endPDF(outfname) if outfname != "-" : percent.done() def main(self, arguments, options) : """Refunds jobs.""" if not hasRL : raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org" if not hasPIL : raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads" self.adminOnly() self.pagesize = getPageSize(options.pagesize) if (not options.reason) or (not options.reason.strip()) : raise PyKotaCommandLineError, _("Refunding for no reason is forbidden. Please use the --reason command line option.") extractonly = {} for filterexp in arguments : if filterexp.strip() : try : (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")] filterkey = filterkey.lower() if filterkey not in self.validfilterkeys : raise ValueError except ValueError : raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp else : extractonly.update({ filterkey : filtervalue }) percent = Percent(self) outfname = options.output.strip().encode(sys.getfilesystemencoding()) if outfname != "-" : percent.display("%s..." % _("Extracting datas")) else : options.force = True self.printInfo(_("The PDF file containing the receipts will be sent to stdout. --force is assumed."), "warn") username = extractonly.get("username") if username : user = self.storage.getUser(username) else : user = None printername = extractonly.get("printername") if printername : printer = self.storage.getPrinter(printername) else : printer = None start = extractonly.get("start") end = extractonly.get("end") (start, end) = self.storage.cleanDates(start, end) jobs = self.storage.retrieveHistory(user=user, printer=printer, hostname=extractonly.get("hostname"), billingcode=extractonly.get("billingcode"), jobid=extractonly.get("jobid"), start=start, end=end, limit=0) peruser = {} nbjobs = 0 nbpages = 0 nbcredits = 0.0 percent.setSize(len(jobs)) if outfname != "-" : percent.display("\n") for job in jobs : if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) : if options.force : nbpages += job.JobSize nbcredits += job.JobPrice counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 }) counters["nbpages"] += job.JobSize counters["nbcredits"] += job.JobPrice job.refund(options.reason) counters["nbjobs"] += 1 nbjobs += 1 if outfname != "-" : percent.oneMore() else : print _("Date : %s") % str(job.JobDate)[:19] print _("Printer : %s") % job.PrinterName print _("User : %s") % job.UserName print _("JobId : %s") % job.JobId print _("Title : %s") % job.JobTitle if job.JobBillingCode : print _("Billing code : %s") % job.JobBillingCode print _("Pages : %i") % job.JobSize print _("Credits : %.3f") % job.JobPrice while True : answer = raw_input("\t%s ? " % _("Refund (Y/N)")).strip().upper() if answer == _("Y") : nbpages += job.JobSize nbcredits += job.JobPrice counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 }) counters["nbpages"] += job.JobSize counters["nbcredits"] += job.JobPrice job.refund(options.reason) counters["nbjobs"] += 1 nbjobs += 1 break elif answer == _("N") : break print if outfname != "-" : percent.done() self.genReceipts(peruser, options.logo.strip().encode(sys.getfilesystemencoding()), outfname, options.number, options.reason, options.unit or _("Credits")) if outfname != "-" : nbusers = len(peruser) print _("Refunded %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \ % locals() if __name__ == "__main__" : parser = PyKotaOptionParser(description=_("Refunding tool for PyKota."), usage="pkrefund [options] [filterexpr]") parser.add_option("-f", "--force", dest="force", action="store_true", help=_("Doesn't ask for confirmation before refunding.")) parser.add_option("-l", "--logo", dest="logo", default=u"/usr/share/pykota/logos/pykota.jpeg", help=_("The image to use as a logo. The logo will be drawn at the center top of the page. The default logo is %default.")) parser.add_option("-p", "--pagesize", type="string", action="callback", callback=checkandset_pagesize, dest="pagesize", default=u"A4", help=_("Set the size of the page. Most well known page sizes are recognized, like 'A4' or 'Letter' to name a few. The default page size is %default.")) parser.add_option("-n", "--number", dest="number", type="int", action="callback", callback=checkandset_positiveint, default=1, help=_("Sets the number of the first receipt. This number will automatically be incremented for each receipt. The default value is %default.")) parser.add_option("-o", "--output", dest="output", type="string", default=u"-", help=_("The name of the file to which the PDF receipts will be written. If not set or set to '%default', the PDF document will be sent to the standard output.")) parser.add_option("-r", "--reason", dest="reason", type="string", help=_("The reason why there was a refund.")) # TODO : due to Python's optparse.py bug #1498146 fixed in rev 46861 # TODO : we can't use 'default=_("Credits")' for this option parser.add_option("-u", "--unit", dest="unit", type="string", help=_("The name of the unit to use on the receipts. The default value is 'Credits' or its locale translation.")) parser.add_filterexpression("username", _("User's name")) parser.add_filterexpression("printername", _("Printer's name")) parser.add_filterexpression("hostname", _("Host's name")) parser.add_filterexpression("jobid", _("Job's id")) parser.add_filterexpression("billingcode", _("Job's billing code")) parser.add_filterexpression("start", _("Job's date of printing")) parser.add_filterexpression("end", _("Job's date of printing")) parser.add_example('--output /tmp/receipts.pdf jobid=503', _("This would refund all jobs which Id is 503. A confirmation would be asked for each job to refund, and a PDF file named /tmp/receipts.pdf would be created containing printable receipts. BEWARE of job ids rolling over if you reset CUPS' history.")) parser.add_example('--reason "Hardware problem" jobid=503 start=today-7', _("This would refund all jobs which id is 503 but which would have been printed during the past week. The reason would be marked as being an hardware problem.")) parser.add_example('--force username=jerome printername=HP2100', _("This would refund all jobs printed by user jerome on printer HP2100. No confirmation would be asked.")) parser.add_example('--force printername=HP2100 start=200602 end=yesterday', _("This would refund all jobs printed on printer HP2100 between February 1st 2006 and yesterday. No confirmation would be asked.")) (options, arguments) = parser.parse_args() run(parser, PKRefund)