219 lines
7.2 KiB
Python
Executable file
219 lines
7.2 KiB
Python
Executable file
#!/usr/bin/python
|
|
# -*- encoding: utf-8 -*-
|
|
#canon_wrapper.py
|
|
"""
|
|
.. codeauthor:: Daniel STAN <dstan@crans.org>
|
|
.. codeauthor:: Antoine Durand-Gasselin <adg@crans.org>
|
|
|
|
Pour envoyer des jobs d'impression à l'imprimante canon via son interface web.
|
|
|
|
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):
|
|
"""Type function for a given range"""
|
|
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():
|
|
"""Make argparse object"""
|
|
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):
|
|
"""Effectively compatibilize then print the file"""
|
|
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
|
|
|
|
else:
|
|
parser = build_parser()
|
|
__doc__ += '''\nHelp message : ::\n\n'''
|
|
__doc__ += '\n'.join(' ' + l for l in parser.format_help().split('\n')) + "\n"
|