scripts/impression/impression_hp.py
2014-11-18 13:22:04 +01:00

505 lines
15 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
.. 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
"""
# impression_hp.py
#
# Classe impression pour HP MFP 880
#
# Copyright (c) 2006, 2007, 2008, 2009 by Cr@ns (http://www.crans.org)
# #############################################################
import sys, os
import logging
import logging.handlers
if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts')
from base import FichierInvalide, SoldeInsuffisant, PrintError, SettingsError
from gestion.config import impression as config_impression
from subprocess import Popen, PIPE, check_output
import livret
##
logger = logging.getLogger('impression_hp')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(name)s: [%(levelname)s] %(message)s')
handler = logging.handlers.SysLogHandler(address = '/dev/log')
try:
handler.addFormatter(formatter)
except AttributeError:
handler.formatter = formatter
logger.addHandler(handler)
DEBUG = bool(os.getenv('DBG_PRINTER', False))
class Option(object):
# Valeur par défaut
value = None
def __str__(self):
return str(value)
name = "option"
pretty_name = u"option quelconque"
def parse(self, v):
self.value = v
class BooleanOption(Option):
value = False
def parse(self, v):
self.value = bool(v)
if str(v).lower() == 'false':
self.value = False
class Couleur(BooleanOption):
name = "couleur"
pretty_name = u"impression couleur"
class Livret(BooleanOption):
name = "livret"
pretty_name = u"livret piqûres à cheval"
def nb(self):
return 2*int(self.value)
class Duplex(BooleanOption):
name = "recto_verso"
pretty_name = u"Impression recto verso"
class Copies(Option):
name = "copies"
pretty_name = u"nombre d'exemplaires"
value = 1
def parse(self, v):
self.value = int(v)
class SelectOption(Option):
possibilities={
None: u'Aucun',
}
value = None
def parse(self, v):
if v == 'None':
v = None
if v not in self.possibilities:
return
self.value = v
class Agrafage(SelectOption):
name = "agrafage"
pretty_name = u"agrafage"
possibilities = {
None : "aucune agrafe",
"TopLeft" : u"agrafe en haut à gauche",
"TopRight" : u"agrafe en haut à droite",
#"BottomLeft" : u"agrafe en bas à gauche", # n'existe pas
#"BottomRight" : u"agrafe en bas à droite", # n' existe pas
"Left": u"deux agrafes sur le bord gauche",
"Right" : u"deux agrafes sur le bord droit",
#"Top" : u"deux agrafes sur le bord supérieur", #pas faisable
#"Bottom" : u"deux agrafes sur le bord inférieur", #n'existe pas
}
def nb(self):
if not self.value:
return 0
if 'Left' in self.value or 'Right' in self.value:
return 2 - int('Top' in self.value or 'Bottom' in self.value)
return 2
def HP_name(self):
if 'Top' in self.value:
return '1Staple%sAngled' % (self.value.replace('Top', ''))
else:
return '2Staples%s' % self.value
class Papier(SelectOption):
name = "papier"
pretty_name = u"format papier"
value = 'A4'
possibilities = {
'A4' : "Papier A4 ordinaire",
'A3' : "Papier A3 ordinaire",
}
class Perforation(SelectOption):
name = "perforation"
pretty_name = u"perforation"
possibilities = {
None: u"Aucune",
"2HolePunchLeft": u"2HolePunchLeft",
"2HolePunchRight": u"2HolePunchRight",
"2HolePunchTop": u"2HolePunchTop",
"2HolePunchBottom": u"2HolePunchBottom",
"3HolePunchLeft": u"3HolePunchLeft",
"3HolePunchRight": u"3HolePunchRight",
"3HolePunchTop": u"3HolePunchTop",
"4HolePunchLeft": u"4HolePunchLeft",
"4HolePunchRight": u"4HolePunchRight",
"4HolePunchTop": u"4HolePunchTop",
}
# ######################################################## #
# CLASSE IMPRESSION #
# ######################################################## #
#
#
class impression(object):
"""impression
Un objet impression correspond à un fichier pdf et un adhérent.
"""
# fichier (chemin)
_fichier = ""
# adhérent (instance)
_adh = None
# paramètres
_settings_list = [
Agrafage,
Papier,
Couleur,
Duplex,
Livret,
Copies,
Perforation,
]
_settings = {}
# le prix de l'impression
_prix = 0.0
_pages = 0
# le cout de base encre pour une impression en couleurs/n&b
# (prix pour papier A4)
_base_prix_nb = 0.0
_base_prix_couleurs = 0.0
# Format du pdf, tout droit sorti de pdfinfo
# (les dimensions sont donc en pts)
_format = '(A4)'
_width = 595.28
_height = 841.89
# Jid unique, à définir avant l'impression
_jid = 0
def __init__(self, path_to_pdf, adh = None):
"""impression(path_to_pdf [, adh])
Crée un nouvel objet impression à partir du fichier pdf pointé
par path_to_pdf. Si adh ext donné, il peut être soit une
instance d'un objet adhérent de crans_ldap soit le login de
l'adhérent. Lève l'exception FichierInvalide si le fichier
n'existe pas ou si ce n'est pas un pdf.
"""
# On génère la liste des options
self._settings = {cls.name: cls() for cls in self._settings_list}
self._fichier = path_to_pdf
self._adh = adh
# on verifie que le fichier existe
if not os.path.isfile(path_to_pdf):
raise FichierInvalide, ("Fichier introuvable", path_to_pdf)
if not open(path_to_pdf).read().startswith("%PDF"):
raise FichierInvalide, ("Le fichier ne semble pas etre un PDF", path_to_pdf)
# on compte les pages et on regarde le format
pdfinfo = Popen(["pdfinfo",self._fichier],stdout=PIPE,stderr=PIPE).communicate()
if pdfinfo[1] <> '':
raise FichierInvalide(u"pdfinfo n'arrive pas a lire le fichier (il est peut-etre corrompu ou protege par un mot de passe), https://wiki.crans.org/VieCrans/ImpressionReseau#Format_des_fichiers",path_to_pdf)
self._pages = -1
for line in pdfinfo[0].split('\n'):
if line.startswith('Pages'):
self._pages = int(line.split()[1])
elif line.startswith('Page size'):
size = line.split()
if len(size) <= 6:
self._format = "Unknown"
else:
self._format = size[6]
self._width = float(size[2])
self._height = float(size[4])
# Hack pour mieux reconnaître les formats
w = min(self._width,self._height)
h = max(self._width,self._height)
err = 100
if abs(w - 595) < err and abs(h - 842) < err:
self._format = "(A4)"
elif abs(w - 842) < err and abs(h - 1180) < err:
self._format = "(A3)"
if self._pages <= 0:
raise FichierInvalide(u"Impossible de lire le nombre de pages",path_to_pdf)
if not self._format in ['(A4)','(A3)']:
pass
#raise FichierInvalide, u"Seuls les formats A3 et A4 sont supportes"
# calcule le prix de l'encre tout de suite
self._calcule_prix()
def _uniq_jid(self):
""" Alloue un jid unique """
fname = '/var/impression/jid'
## Maybe need a lock ?
try:
f = file(fname,'r+')
cur = int(f.read())+1
f.seek(0)
except (IOError,ValueError):
cur = 0
f = file(fname,'w')
f.write(str(cur))
f.close()
return cur
def _pdfbook(self):
"""Génère un pdf livret équivalent et renvoie le chemin"""
newfile = '/tmp/' + self.fileName()[-4] + '-book.pdf'
newfile = '/tmp/book.pdf'
check_output(
['/usr/bin/pdfjam', self._fichier,
livret.pdfjam_order(self._pages),
'-o', newfile,
])
return newfile
def changeSettings(self, **kw):
"""changeSettings([keyword=value])
Change les parametres de l'impression, recalcule et renvoie le nouveau prix.
"""
for name in kw:
if name not in self._settings:
raise Exception('unknown %s option' % name)
self._settings[name].parse(kw[name])
if self._settings['livret'].value:
self._settings['recto_verso'].parse(True)
self._settings['agrafage'].parse(None)
# TODO
# Check si l'agrafage et cie sont compatibles avec le nb de pages
return self._calcule_prix()
def printSettings(self):
"""printSettings()
Affiche les paramètres courants sur la sortie standard
"""
for k in self._settings:
opt = self._settings[k]
label = u"%s: %s" % (opt.pretty_name.capitalize(), opt.value)
print label.encode('UTF-8')
def prix(self):
"""prix()
Renvoie le prix courrant de l'impression
"""
return self._prix
def fileName(self):
"""fileName()
renvoie le nom du fichier pdf (exemple : monPdf.pdf)
"""
return os.path.basename(self._fichier)
def filePath(self):
"""filePath()
renvoie le chemin d'accès au fichier pdf.
"""
return self._fichier
def pages(self):
"""pages()
renvoie le nombre de pages du document (page au sens nombre de
faces à imprimer et non le nombre de feuilles)
"""
return self._pages
def imprime(self):
"""imprime()
imprime le document pdf. débite l'adhérent si adhérent il y a.
(si il a été indiqué à l'initialisation de l'objet)
"""
self._jid = self._uniq_jid()
# debite l'adhérent si adherent il y a
if not DEBUG and (self._adh != None):
adh = self._adh.split('@')
if len(adh) > 1:
adh = adh[1:]
adh = self._get_adh(adh[0])
self._calcule_prix() # Normalement inutile, mais évite les races
if (self._prix > (adh.solde() - config_impression.decouvert)):
raise SoldeInsuffisant
adh.solde(-self._prix, "impression(%d): %s par %s" % (self._jid,self._fichier,self._adh))
adh.save()
del adh
# imprime le document
self._exec_imprime()
def _calcule_prix(self):
faces = self._pages
if self._settings['livret'].value:
feuilles = int((faces+3)/4)
faces = 2 * feuilles
elif self._settings['recto_verso'].value:
feuilles = int(faces/2.+0.5)
else:
feuilles = faces
if (self._settings['papier'].value == "A3"):
c_papier = config_impression.c_a3
pages = 2*faces
else:
pages = faces
c_papier = config_impression.c_a4
if self._settings['couleur'].value:
c_impression = c_papier * feuilles + config_impression.c_face_couleur * pages
else:
c_impression = c_papier * feuilles + config_impression.c_face_nb * pages
# Cout des agrafes
nb_agrafes = self._settings['agrafage'].nb() + self._settings['livret'].nb()
c_agrafes = nb_agrafes * config_impression.c_agrafe
c_total = int(self._settings['copies'].value * ( c_impression +
c_agrafes ) + 0.5) # arrondi et facture
self._prix = float(c_total)/100
return self._prix
def _get_adh(self, adh):
if type(adh) == str:
#from ldap_crans_test import crans_ldap
from gestion.ldap_crans import CransLdap
adh = CransLdap().getProprio(adh, 'w')
return adh
## ################################# ##
## fonction qui imprime pour de vrai ##
## ################################# ##
##
def _exec_imprime(self):
""" Envoie l'impression a l'imprimante avec les parametres actuels """
if (self._adh != None):
logger.info('Impression(%d) [%s] : %s' % (self._jid, self._adh, self._fichier))
else:
logger.info("Impression(%d) : %s" % (self._jid, self._fichier))
# Création de la liste d'options pour lp
options = list()
# Pour spécifier l'imprimante
options += ['-d', 'MFPM880']
# 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("\"","\\\""))
options += ['-t', jobname]
#Pour le nombre de copies et specifie non assemblee
options += ['-n', str(self._settings['copies'].value)]
#Et on ne les veux pas assemblées (sic)
options += ['-o', 'Collate=True']
# Pour donner le login de l'adherent (TODO: useful ?)
#options += ['-U', str(self._adh)]
if self._settings['papier'].value == 'A4':
options += ['-o', 'PageSize=A4']
else:
options += ['-o', 'pdf-expand',
'-o', 'pdf-paper=841x1190',
'-o', 'PageSize=A3']
# Toujours resize (pour éviter que l'imprimante ne deadlock)
options += ['-o', 'fit-to-page']
# Quelle alimentation papier utiliser ?
options.append('-o')
if self._settings['papier'].value == 'A3':
options.append('InputSlot=Tray1')
elif self._settings['livret'].value:
options.append('InputSlot=Tray2')
else:
options.append('InputSlot=Tray3')
if not self._settings['couleur'].value:
options += ['-o', 'HPColorAsGray=True']
duplex = self._settings['recto_verso'].value or\
self._settings['livret'].value
paysage = self._width > self._height
# mode paysage: on indique que c'est landscape (ou livret)
if paysage:
options += ['-o', 'landscape']
# Livret: 2 pages par côté, et on inverse le bord duplex
if self._settings['livret'].value:
options += ['-o', 'number-up=2']
paysage = not paysage
if duplex:
if paysage:
options += ['-o', 'sides=two-sided-short-edge']
else:
options += ['-o', 'sides=two-sided-long-edge']
if not duplex:
options += ['-o', 'sides=one-sided']
if self._settings['livret'].value:
options += ['-o', 'HPStaplerOptions=FoldStitch']
elif self._settings['agrafage'].value:
v = self._settings['agrafage'].HP_name()
options += ['-o', 'HPStaplerOptions=%s' % v]
# Que se passe-t-il si le nom de fichier commence par - ?
# pour éviter cela, on indique la fin des options, avec "--"
options.append('--')
if self._settings['livret'].value:
options.append(self._pdfbook())
else:
options.append(self._fichier)
if not DEBUG:
logger.info("lp " + " ".join(options))
check_output(['lp'] + options)
else:
logger.info("pretend printing (debug): " + " ".join(options))