#!/usr/bin/python # -*- encoding: utf-8 -*- # canon_wrapper.py # Authors: Daniel STAN # Antoine Durand-Gasselin # License: GPLv3 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) filename = args.jobname if not filename.endswith('.pdf'): filename += '.pdf' req = requests.post(build_url('do_print'), cookies=cook, data=opt, files={'File': (filename, 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