From ebe94de0944bc265f5f4e2b91ec41b22f9d11aa6 Mon Sep 17 00:00:00 2001 From: Daniel STAN Date: Sun, 17 Mar 2013 21:01:56 +0100 Subject: [PATCH] [impression] impression via l'interface web --- impression/canon_wrapper.py | 200 +++++++++++++++++++++++++++++++ impression/impression_canon.py | 6 +- impression/impression_wrapper.py | 43 +++++++ 3 files changed, 247 insertions(+), 2 deletions(-) create mode 100755 impression/canon_wrapper.py create mode 100644 impression/impression_wrapper.py diff --git a/impression/canon_wrapper.py b/impression/canon_wrapper.py new file mode 100755 index 00000000..66d1d219 --- /dev/null +++ b/impression/canon_wrapper.py @@ -0,0 +1,200 @@ +#!/usr/bin/python +# -*- encoding: utf-8 -*- + +import requests #pip install requests +# See: +# http://docs.python-requests.org/en/latest/user/quickstart/#post-a-multipart-encoded-file +import os, os.path, sys +import time +import argparse +import types +import tempfile, atexit +import subprocess +from syslog import syslog, openlog + + +BASE_URL = "http://imprimante.adm.crans.org" +URIs = { + 'root': '/', + 'ended_jobs_csv': '/pprint.csv?Flag=Csv_Data&LogType=0&Dummy=%(timestamp)s', + 'current_jobs': '/jpl.cgi?Flag=Init_Data&CorePGTAG=6&Dummy=%(timestamp)s', + 'print': '/ppdf.cgi?Type=PDF&Dummy=%(timestamp)s', + 'do_print': '/pprint.cgi', +} + +STAPLE_POSITIONS={"TopLeft":"5", "TopRight":"6", "BottomLeft":"7", + "BottomRight":"8", "Top":"1", "Bottom":"2", "Left":"3", + "Right":"4", "None":"0", "Book":"9",} + +OPTIONS = { + "Url": "http://", + "Mode": 100, + "ManualNo": 0, + "DocPasswd": "", + "WebUserName": '', + "WebPasswd": '', + "PageMode": {'all': 0, 'range': 1, 'default': 0}, + "StartPage": 1, + "EndPage": 1, + "Copies": lambda args: args.copies, + "MediaSize": lambda args: { + 'auto': 0, 'default': 0, + "a4": 5, "a3": 6, "a5": 16, "b4": 12, "b5": 13, + "ltr": 1, "lgl": 2, "11x17": 3, "exec": 4, "com-10": 7, + "monarch": 8, "c5 env": 9, "b5 env": 10, "dl env": 11, + }.get(args.page_size.lower()), + "MediaType": 0, # TODO + "ManualFeed": 0, + "FitSize": lambda args: {'no': 0, 'yes': 1, 'default': 1}.get(args.expand), + #expand to mediasize + "DuplexType": lambda args: + {'book': 0, 'one-sided': 0, 'short-edge': 1, + 'long-edge': 2}.get(args.duplex_type), + # NB: book => one-sided (sinon, ça peut merder) + "Sort": {'none': 0, 'finition': 1, 'default': 1}, + #finition: pour tout ce qui a une finition + # TODO + "PunchPos": 0, + "StapleType": lambda args: STAPLE_POSITIONS['Book'] if args.duplex_type == 'book' + else STAPLE_POSITIONS[args.staple_position], + "BookType": lambda args: 0 if args.duplex_type == 'book' else 2, + "Annotation": 2, + "ColorMode": lambda args: {'yes': 2, 'no': 1}.get(args.colors), + "C_Render": 0, "C_RGB_Pro": 1, "C_CMYK_Pro": 4, "C_OUT_Pro": 1, + "C_GRAY_Pro": 1, "C_Matching": 0, + "SPOT_Color": 1, + "C_Pure_B": 1, "C_B_OVPrn": 1, "C_Bright": 100, "C_Gray_Com": 1, + "C_OVR_EFF": 1, + "WidePrn": 0, + "NupPrint": 0, "NupStart": 0, + "Resolution": 1, + "HalfToneTxt": 1, "HalfToneGrp": 1, "HalfToneImg": 1, + "AstIntensity": 2, "AstText": 0, "AstGrap": 1, "AstImag": 1, + "StoreBox": lambda args: int(args.test), + "BoxNo": 17, + "RGBDLName": "", + "CMYKDLName": "", + "OUTDLName": "", + "BOXName": lambda args: args.jobname + '_%d' % int(time.time()), + "Flag": "Exec_Data_Pdf", + "Dummy": lambda _: int(time.time()), + "Direct": 100, +} + +def build_options(args): + opt = {} + for (k,v) in OPTIONS.iteritems(): + if isinstance(v, types.FunctionType): + v = v(args) + if isinstance(v, dict): + v = v['default'] + if not isinstance(v, file): + v = str(v) + opt[k] = v + return opt + + +def build_url(name): + """ Build url from a page name (see URIs) """ + return (BASE_URL + URIs[name]) % { + 'timestamp': int(time.time()), + } + + +def range_checker(a, b): + def cast(x): + try: + v = int(x) + except ValueError(err): + raise argparse.ArgumentTypeError(str(err)) + if v not in xrange(a, b): + raise argparse.ArgumentTypeError("should be between %d and %d" % (a, b)) + return v + return cast + +def build_parser(): + parser = argparse.ArgumentParser( + description="HTTP printing driver for irc3580, with compatibility mode.", + epilog="logs are sent to syslog (eg /var/log/canon_wrapper.log)", + prog='canon_wrapper', + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument('--jobname', help="job name as seen by the printer", + default="unamed job", + ) + parser.add_argument('--copies', default=1, + help="number of copies", + type=range_checker(1,1000), + ) + parser.add_argument('--colors', type=str, default='yes', const="yes", + nargs="?", + choices=["yes", "no"], + ) + parser.add_argument('--expand', type=str, default='yes', const="yes", + nargs="?", + choices=["yes", "no"], + help="expand to page size", + ) + parser.add_argument('--duplex-type', type=str, default='one-sided', + choices=["one-sided", "short-edge", "long-edge", + "book"], + ) + parser.add_argument('--page-size', type=str, default='A4', + choices=["A3", "A4"], + help="page size", + ) + parser.add_argument('--test', default=False, const=True, + action='store_const', + help="Don't really print, send to box folder", + ) + parser.add_argument('--staple-position', default='None', + choices=STAPLE_POSITIONS.keys(), + help="Where to staple", + ) + parser.add_argument('filename') + return parser + +def do_print(args): + opt=build_options(args) + syslog('Options: %s' % repr(opt)) + #syslog('Starting ghostscript compat, piped mode') + + temppdf = tempfile.NamedTemporaryFile(suffix='.pdf') + syslog('Starting ghostscript compat to %s' % temppdf.name) + atexit.register(temppdf.close) + proc = subprocess.Popen(['/usr/bin/gs','-dCompatibilityLevel=1.2', + '-dBATCH','-dNOPAUSE','-sDEVICE=pdfwrite', + '-sOutputFile=' + temppdf.name, + args.filename]) + # '-sOutputFile=%stdout%', '-q', + # args.filename], stdout=subprocess.PIPE) + ret = proc.wait() + syslog('ghostscript compat finished with ret=%d' % ret) + if ret != 0: + syslog('aborting.') + return ret + syslog('Getting cookie...') + cook = requests.get(build_url('root')).cookies + syslog('Sending pdf (%d bytes)...' % os.stat(temppdf.name).st_size) + temppdf.seek(0) + req = requests.post(build_url('do_print'), cookies=cook, data=opt, + files={'File': (args.jobname + '.pdf', temppdf)}) + #files={'File': (args.jobname + '.pdf', proc.stdout)}) + temppdf.close() + if "ms_err.gif" in req.text: + syslog("Printer side ERROR !") + return 42 + syslog('Compatibilized pdf successfully sent. Continue tracking on the printer !') + return 0 + +if __name__ == '__main__': + parser = build_parser() + args = parser.parse_args() + if args: + try: + openlog('canon_wrapper[%s]' % args.jobname) + sys.exit(do_print(args)) + except Exception as e: + syslog('Caught exception %s' % repr(e)) + raise + diff --git a/impression/impression_canon.py b/impression/impression_canon.py index 88d246c8..7160e212 100644 --- a/impression/impression_canon.py +++ b/impression/impression_canon.py @@ -79,7 +79,8 @@ AVAIL_AGRAFES = ["None", "TopLeft", "TopRight", "Left", "BottomLeft", "BottomRig DICT_PAPIER = { 'A4' : "Papier A4 ordinaire", 'A3' : "Papier A3 ordinaire", - 'A4tr' : "Transparent A4" } + #'A4tr' : "Transparent A4", + } # ######################################################## # # ERREURS # @@ -227,7 +228,8 @@ class impression: raise FichierInvalide(u"Impossible de lire le nombre de pages",path_to_pdf) if not self._format in ['(A4)','(A3)']: - raise FichierInvalide, u"Seuls les formats A3 et A4 sont supportes" + pass + #raise FichierInvalide, u"Seuls les formats A3 et A4 sont supportes" # calcule le prix de l'encre tout de suite self._calcule_prix() diff --git a/impression/impression_wrapper.py b/impression/impression_wrapper.py new file mode 100644 index 00000000..ebaccd5e --- /dev/null +++ b/impression/impression_wrapper.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import subprocess + +from impression_canon import DECOUVERT_AUTHORISE, DICT_AGRAFAGE, AVAIL_AGRAFES,\ + DICT_PAPIER, FichierInvalide, SoldeInsuffisant, PrintError, SettingsError +import impression_canon + +class impression(impression_canon.impression): + """ Extension de la classe d'impression de l'imprimante canon, + la seule méthode ayant un comportement différent est _exec_imprime + qui fait appel à canon_wrapper.py au lieu de lp""" + + def _exec_imprime(self): + """ Envoie l'impression à l'imprimante avec les parametres actuels """ + + opt=[] + if (self._adh != None): + self.log.info('Impression(%d) [%s] : %s' % (self._jid, self._adh, self._fichier)) + else: + self.log.info("Impression(%d) : %s" % (self._jid, self._fichier)) + + # Pour spécifier un jobname de la forme jid:adh:nom_du_fichier + jobname = '%d:%s:%s' % (self._jid, self._adh, self._fichier.split('/')[-1].replace("\"","\\\"")) + opt += ['--jobname', jobname] + + opt += ['--page-size', self._settings['papier']] + + opt += ['--colors', 'yes' if self._settings['couleur'] else 'no'] + + if self._settings['livret']: + opt += ['--duplex-type', 'book'] + else: # Not livret + if self._settings['recto_verso']: + opt += ['--duplex-type', ('long' if self._settings['portrait'] + else 'short')+'-edge'] + else: + opt += ['--duplex-type', 'one-sided'] + opt += ['--staple-position', self._settings['agrafage']] + opt.append(self._fichier) + self.log.info("Impression(%d) : %s" % (self._jid, repr(opt))) + subprocess.Popen(['/usr/scripts/impression/canon_wrapper.py'] + opt)