scripts/impression/class_impression.py
bobot 5dc32794e4 Ajout de commentaire valide avec pydoc
darcs-hash:20060711185813-9e428-f72ed7e9cbf83f1f3129b0496e5bf5c07a069ae3.gz
2006-07-11 20:58:13 +02:00

522 lines
22 KiB
Python
Executable file

#! /usr/bin/env python
# -*- coding: iso-8859-15 -*-
"""
Class pour l'impression depuis le web par cups
Codé par Francois
Inspiré par le backend écrit par Benoit, Fred et Brice, inspirés par CUPSPykota
Licence : GNU General Public Licence, version 2
"""
import sys, time, tempfile, os, commands, string, random
sys.path.append('/usr/scripts/impression')
sys.path.append('/usr/scripts/gestion')
from config import impression
from ldap_crans import crans_ldap
from syslog import openlog, syslog, LOG_DEBUG
duree_vie_pdf=3600
#fonction principal: impression (fichier_impression)
################################################################################
class ErreurImpression(Exception):
def __init__(self, value):
self.value=value
def __str__(self):
return repr(self.value)
################################################################################
class test:
def mail(self):
return u"francois.bobot@crans.org"
def Nom(self):
return u"francois"
def solde(self,modif=0.):
return (40.+modif)
def save(self):
pass
################################################################################
class fichier_impression:
""" Interface pour la gestion d'un travaille d'impression """
nom_job = ''
nbr_pages = 1
nb_copies = 1
taille = "A4"
recto_verso = False
transparent = False
couleur = True
cout = 0.0
portrait = True
agrafe=0 #-1 : agrafe diagonale, 0 : pas d'agrafe, 1 : 1 agrafe parallele, 2: 2 agrafes, 3: 3 agrafes, 6: 6 agrafes(stitching)
user = ""
user_ldap = None # sera une instance de l'utilisateur dans la base ldap.
imprime = -1 #-2 impression en suspend, -1 mise dans la file
# d'attente, 0 devis, time pour l'heure, -3 déja
# imprimé ou devis fait depuis trops longtemps : le
# pdf n'est plus disponible
nom_fichier_pdf = "" # chemin et nom du pdf
nom_fichier_desc = "" # chemin et nom du fichier contenant les
# options de l'utilisateur
list_messages = [] # liste d'erreur et de remarque
list_messages_importants = []
list_messages_admin = [] # liste d'erreur réservé au administrateur
erreur_critique = False # True si une erreur empêchant
# l'impression est apparue
code = None # code pour le digicode
modif_epoch = 0.0 # moment de création du job ou devis. exprimé
# en epoch (float) : nombre de seconde depuis
# la référence du temps unix (+-=1970)
job_id = None # champs pour de futures améliorations
def actualise_cout(self):
""" Actualise le champ cout
Leve l'erreur ErreurImpression si le calcul du cout ne c'est pas bien passe """
# Calcul du cout de l'impression :
retour = cout(self)
# Met le prix dans cout s'il n'y a pas d'erreur
if type(retour) is float:
self.cout = retour
self.list_messages_importants.append('cout')
else:
self.erreur_critique = True
self.list_messages_importants.append('erreur_critique')
self.list_messages_admin.append('erreur_cout')
raise ErreurImpression(retour)
def test_cout(self):
""" Repond vrai si le solde est assez élevé. """
if self.user_ldap == None:
self.user_ldap = utilisateur(self.user, False)
# /!\ decouvert est négatif.
return not (self.cout > (self.user_ldap.solde() - impression.decouvert))
def fait_payer(self):
""" Retire au solde de l'adherent la valeur de cout """
self.user_ldap = utilisateur(self.user, True)
self.user_ldap.solde(-self.cout, self.nom_job)
self.user_ldap.save()
return not (self.cout > (self.user_ldap.solde() - impression.decouvert))
def gen_code(self):
""" Genere le code et l'enregistre dans /var/impression/codes pour radius """
# Génération du code et écriture du code
rand=random.Random()
# Graine automatique avec le temps
rand.seed()
for i in range(1000):
# On génère un code
code = rand.randint(100000, 999999)
# Si le code est libre, on sort de la boucle
if not os.path.exists("/var/impression/codes/%d" % code):
break
else:
# Pas de code disponible
print ("ERROR: Il n'y a pas de code disponible" )
sys.stderr.write ("ERROR: Il n'y a pas de code disponible" )
try:
self.list_messages_importants.append('erreur gen_code')
sys.stderr.write("DEBUG: Un rapport de bug a ete automatiquement envoye.\n")
except:
sys.stderr.write("ERROR: Impossible d'envoyer le rapport de bug.\n")
sys.stderr.write("ERROR: Plus de codes disponibles.\n")
sys.stderr.write("ERROR: Penser a ouvrir a l'adherent debite...\n")
return
# On enregistre le fichier avec le code pour numéro
codefichier = open("/var/impression/codes/%d" % code, 'w')
codefichier.write("Utilisateur %s\n" % self.user)
codefichier.close()
self.code = code
self.list_messages_importants.append('code')
def impression(self):
""" Envoie l'impression a l'imprimante avec les parametres actuels """
# Envoi du fichier à CUPS
options =''
# Création de la liste d'options
# pour le nombre de copies et specifie non assemblee
#options += '-# %d -o Collate=True' % self.nb_copies
# Pour spécifier l'imprimante
options += ' -P laserjet'
# Pour spécifier le bac sortie
options += ' -o OutputBin=Left'
# Pour spécifier la version du language postscript utilisé par pdftops
options += ' -o pdf-level3'
# Pour donner le titre de l'impression
options += " -T '%s'" % self.nom_job
# Pour donner le login de l'adherent
options += ' -U %s' % self.user
# Pour demander une page de garde
#options += ' -o job-sheets=crans' #page de garde de type standard
#options += " -o job-billing=%.2f" % self.cout
#options += ' -o job-sheets=none'
#Indique la présence d'un bac de sortie avec agrafeuse
options += " -o Option20=MBMStaplerStacker -o OutputBin=StackerDown"
if self.agrafe==-1:
options += ' -o StapleLocation=1diagonal'
elif self.agrafe==0:
options += ' -o StapleLocation=None'
elif self.agrafe==1:
options += ' -o StapleLocation=1parallel'
elif self.agrafe==2:
options += ' -o StapleLocation=2parallel'
elif self.agrafe==3:
options += ' -o StapleLocation=3parallel'
elif self.agrafe==6:
options += ' -o StapleLocation=Stitching'
if self.transparent:
options += ' -o InputSlot=Tray1 -o Media=Transparency'
if self.taille == 'A4':
options += ' -o pdf-paper=571x817 -o PageSize=A4'
else:
options += ' -o pdf-paper=825x1166 -o InputSlot=Tray3 -o HPPaperPolicy=A3 -o PageSize=A3'
if self.portrait:
if self.recto_verso:
options += ' -o sides=two-sided-long-edge'
else:
options += ' -o sides=one-sided'
else:
options += ' -o landscape'
if self.recto_verso:
options += ' -o sides=two-sided-short-edge'
else:
options += ' -o sides=one-sided'
options += ' -o HPColorasGray=%s' % (not self.couleur)
liste_nom_fichier_pdf=(' '+self.nom_fichier_pdf)*self.nb_copies
#(status,rep) = commands.getstatusoutput("lpr %s %s" % (options, self.nom_fichier_pdf))
(status,rep) = commands.getstatusoutput("lpr %s %s" % (options, liste_nom_fichier_pdf))
if status != 0:
print "<p>status:%d rep: %s</p>" % (status, rep)
openlog("impression")
syslog("lpr status:%d | rep: %s" % (status, rep))
def corrige(self):
""" Corrige les absurditees (ex transparents recto-verso) [obsolete] """
# Trouve le nombre de pages
self.nbr_pages = int(os.popen("pdfinfo %s | grep Pages " % (self.nom_fichier_pdf)).readline().split()[1])
# Corrige les aberrations
# Correction des aberations (transparent et recto_verso :) )
# Priorité des informations par ordre décroissant
# Transparent, paper, nbr_page, recto_verso
if (self.taille != 'A4') & (self.taille != 'A3'):
self.list_messages_importants.append("paper")
self.erreur_critique=True
if self.transparent:
if self.paper != 'A4':
self.list_messages.append("A3 transparent")
self.paper = 'A4'
if self.recto_verso:
self.list_messages.append("Recto_verso transparent")
self.recto_verso = False
if self.nbr_pages == 1 and self.recto_verso:
self.list_messages.append("Recto_verso 1 page")
self.recto_verso = False
def affiche_messages_html(self):
""" Creait le code html correspondant a un tableau contenant le code adequate [obsolete] """
body = ""
"""
if self.imprime:
body=string.join(map ((dico_message_laserjet.get),self.list_messages_importants))
if len(self.list_messages)<>0:
body=body+'\nRemarques:\n'+string.join(map ((dico_message_laserjet.get),self.list_messages))
else:
body=string.join(map ((dico_message_devis.get),self.list_messages_importants))
body=body+'\nRemarque:\n'+string.join(map ((dico_message_devis.get),self.list_messages))
body=body % {'taille' : self.taille, 'code' : self.code, 'solde' : self.user_ldap.solde(), 'cout' : self.cout , 'adresse_imprimante' : impression.From_imprimante}
"""
body = string.join(self.list_messages_importants)
return "<p>%s</p>" % body
def enregistre_pdf(self, f_value, f_nom, dossier):
""" Enregistre un pdf dans le dossier dossier
Met a jour la variable nom_fichier_pdf
dossier = dossier de destination
f_value = le coprs du pdf
f_nom = obsolete (etait le prefixe du nom du fichier)"""
openlog("impression")
try:
(fd_fichier_desc, self.nom_fichier_desc) = tempfile.mkstemp(suffix='.desc', prefix='job', dir=dossier)
os.close(fd_fichier_desc)
except Exception, inst:
raise ErreurImpression("class :erreur dans creation de desc %s : %s" % (self.nom_fichier_desc, str(inst)))
self.nom_fichier_pdf = self.nom_fichier_desc.replace('.desc', '.pdf')
try:
fd_fichier_pdf = open(self.nom_fichier_pdf, 'w')
fd_fichier_pdf.write(f_value)
fd_fichier_pdf.close()
except Exception, inst:
raise ErreurImpression("class :erreur dans enregistrement du .pdf %s: %s" % (self.nom_fichier_pdf,str(inst)))
os.chmod(self.nom_fichier_pdf, 0640)
os.chmod(self.nom_fichier_desc, 0640)
self.nbr_pages = int(os.popen("pdfinfo '%s' | grep Pages " % (self.nom_fichier_pdf)).readline().split()[1])
self.modif_epoch = time.time()
def sauve_desc(self):
""" Sauve les parametre du travaille d'impression dans le fichier nom_fichier_desc """
try:
file_obj_desc = open(self.nom_fichier_desc,'w')
for key in ("erreur_critique", "nom_job", "nbr_pages",
"nb_copies","taille", "recto_verso",
"transparent", "couleur", "cout",
"portrait", "user", "imprime",
"nom_fichier_pdf", "nom_fichier_desc",
"code", "modif_epoch", "job_id","agrafe"):
file_obj_desc.write(key+"="+str(getattr(self,key))+"\n")
file_obj_desc.close()
except Exception, inst :
raise ErreurImpression("class : erreur dans enregistrement du .desc %s : %s" % (self.nom_fichier_desc,str(inst)))
def read_desc(self):
""" Lit le fichier nom_fichier_desc et met a jour les variables """
try:
file_obj_desc = open(self.nom_fichier_desc, 'r')
ligne = file_obj_desc.readline()[0:-1]
while ligne != "":
parse = ligne.split("=")
if parse[1] == 'None':
setattr(self,parse[0],None)
elif parse[0] in ("nom_job", "taille", "user", "nom_fichier_pdf", "nom_fichier_desc", "job_id"):
#pour les attributs strings
setattr(self,parse[0],parse[1])
elif parse[0] in ("nbr_pages", "nb_copies", "imprime", "code","agrafe"):
#pour les attributs entiers
setattr(self,parse[0],int(parse[1]))
elif parse[0] in ("recto_verso", "transparent", "couleur", "portrait", "erreur_critique"):
#pour les attributs boolean
setattr(self,parse[0],(parse[1] == 'True'))
elif parse[0] in ("cout", "modif_epoch"):
#pour les attributs flottants
setattr(self, parse[0], float(parse[1]))
else:
self.erreur_critique = True
print '<p>ligne non parsée: %s</p>' % ligne
ligne=file_obj_desc.readline()[0:-1]
file_obj_desc.close()
except Exception, inst :
raise ErreurImpression("class : erreur dans l'ouverture du .desc %s : %s" % (self.nom_fichier_desc,repr(inst)))
# Verifie que le fichier pdf existe bien si il doit exister.
if not os.path.exists(self.nom_fichier_pdf):
if self.imprime != -3:
self.imprime = -4
openlog("impression")
syslog("class : %s a disparu" % self.nom_fichier_pdf)
def get_attr(self,key):
""" Fonction obsolete utilise avec cheetah """
return getattr(self,key)
def __repr__(self):
""" Cree le code html correspondant a un tableau contenant les informations pertinentes """
dict_contraire = { 'couleur' : 'Noir et Blanc',
'transparent' : 'Normal',
'portrait': 'Paysage',
'recto_verso' : 'Recto',
'erreur_critique' : "Pas d'erreur" }
dict_normale = { 'couleur' : 'Couleur',
'transparent' : 'Transparent',
'portrait': 'Portrait',
'recto_verso' : 'Recto-Verso',
'erreur_critique' : "Problème survenu" }
dict_agrafe = { -1 : "agrafe en diagonale",
0 : "aucune agrafe",
1 : "1 agrafe parallele",
2 : "reliure 2 agrafe",
3 : "reliure 3 agrafe",
6 : "reliure 6 agrafe" }
corps = "<TABLE BORDER=0 CELLPADDDING=2>\n<TR><th ALIGN=CENTER COLSPAN=2><U>%s</U></th><td ALIGN=RIGHT>%s</TR>\n" % (self.nom_job, time.ctime(0.0))
corps += "<TR><td>\n<TABLE BORDER=2 RULES=ROWS CELLPADDDING=0>\n"
self.code = "%s#" % self.code
for key in ("nbr_pages", "nb_copies", "taille", "code"):
corps += '<TR><td>%s</td><td>%s</td></TR>\n' % (key,
str(getattr(self,key)))
self.code = self.code[:-1]
corps += "</TABLE>\n</td>\n<td>\n<TABLE BORDER=2 RULES=ROWS CELLPADDDING=0>\n"
corps += '<TR><td>%s</td></TR>\n' % dict_agrafe[self.agrafe]
for key in ("couleur", "transparent",
"portrait", "recto_verso"):
if getattr(self, key):
corps += '<TR><td>%s</td></TR>\n' % dict_normale[key]
else:
corps += '<TR><td>%s</td></TR>\n' % dict_contraire[key]
corps += "</TABLE>\n</td><td><U>"
if self.erreur_critique:
corps += "erreur de comptage"
else:
corps += "%s euros" % self.cout
corps += "</U></td></TR>\n</TABLE>"
return corps
################################################################################
# Recuperation de lobjet ldap en lecture ou ecriture de l'adherent ayant le login user
# reprise du backend
# Leve l'erreur ErreurImpression si l'adherent n'existe pas
def utilisateur(user, rw):
""" Renvoie l'adherent qui imprime le job
* user est l'utilisateur (argument n°2 des paramètres passés au backend)
* rw vaut True si on veut modifier le compte LDAP de l'adhérent,
False dans le cas contraire
"""
# Impression en local avec les certificats (possible pour le groupe adm)
if user == "root":
raise ErreurImpression("class : Utilisateur root passe en parametre.")
# Récupération de l'adhérent
try:
base = crans_ldap()
if rw:
res = base.search("login=%s" % user,'w')['adherent']
else:
res = base.search("login=%s" % user)['adherent']
except:
raise ErreurImpression("class : Base LDAP non joignable.")
# Si on ne trouve rien :
if len(res) != 1 :
raise ErreurImpression("class : adherent %s introuvable\n" % user)
adherent = res[0]
openlog("impression")
syslog(LOG_DEBUG,"class : Adherent %s (aid=%s) recupere.\n" % (user, adherent.id()))
return adherent
################################################################################
# Fonction auxilliaire de calcul du cout, reprise directement de l'ancien backend
def cout(fic_impr):
""" Calcule le pourcentage de couleur sur les pages
fic_impr est une instance de fichier_impression
retourn :
un string avec le nom de l'erreur si erreur il y a
un float dans les cas de fonctionnement
"""
# taille peut valoir A3 ou A4
# nb_copies est le nombre de copies désirées
# nom_rep seras le dossier dans tmp ou tous les fichier créé par
# convert seront entreposé
nom_rep = tempfile.mkdtemp(prefix='tmpimpr')
nom_png = "%s/convert.png" % nom_rep # Nom prefixe et chemin des png créé par convert
if (fic_impr.taille == "A3"):
# Une feuille A3 couvre 2 fois plus de surface que A4
c_taille = impression.c_a3
cout_coul = 2*impression.c_coul
cout_noir = 2*impression.c_noir
else:
cout_coul = impression.c_coul
cout_noir = impression.c_noir
if fic_impr.transparent:
c_taille = impression.c_trans
else:
c_taille = impression.c_a4
if (fic_impr.couleur):
# Convertit les pdf en png couleur
(status, rep) = commands.getstatusoutput("nice -n 5 gs -sDEVICE=png16m -r30 -dBATCH -dNOPAUSE -dSAFER -dPARANOIDSAFER -dGraphicsAlphaBits=4 -dTextAlphaBits=4 -dMaxBitmap=50000000 -sOutputFile=%s%%d -q %s" % (nom_png,fic_impr.nom_fichier_pdf))
if status:
return "ERREUR : Fichier invalide. Aucun png n'a ete cree.\n"
# Récupère la liste des fichiers
list_filepng=os.listdir(nom_rep)
# Calcule le nombre de pixel de couleur
remplissage = [0, 0, 0, 0, 0] # C, M, J, N, nombre de pages
for fichier in list_filepng:
resultats = commands.getoutput("nice -n 5 /usr/scripts/impression/percentcolour %s/%s" % (nom_rep, fichier))
l_resultats = resultats.split(":")
for i in [0, 1, 2, 3]:
remplissage[i] += float(l_resultats[i])*float(l_resultats[4])
remplissage[4] += float(l_resultats[4])
total_noir = remplissage[3]
total_couleur = sum(remplissage[0:3])
faces = int(remplissage[4])
if (fic_impr.recto_verso == False):
pages = faces # nb de pages par copies
else:
pages = int(faces/2.+0.5)
if total_couleur > 0:
c_total = (c_taille * pages + (impression.c_tambour_coul +
impression.c_tambour_noir) * faces +
cout_noir * total_noir + cout_coul * total_couleur)
else: # Pas de couleur, malgre l'indication
c_total = (c_taille * pages +
impression.c_tambour_noir * faces+cout_noir * total_noir)
else:
# Convertit les pdf en png
(status, rep) = commands.getstatusoutput("nice -n 5 gs -sDEVICE=pnggray -r30 -dBATCH -dNOPAUSE -dSAFER -dPARANOIDSAFER -dGraphicsAlphaBits=4 -dTextAlphaBits=4 -dMaxBitmap=50000000 -sOutputFile=%s%%d -q %s" % (nom_png, fic_impr.nom_fichier_pdf))
if status:
return "ERREUR : Fichier invalide. Aucun png n'a été créé.\n"
#récupère la liste des fichiers
list_filepng=os.listdir(nom_rep)
remplissage = [0, 0] # Noir, nombre de pages
for fichier in list_filepng:
resultats = commands.getoutput("nice -n 5 /usr/scripts/impression/percentblack '%s/%s'" % (nom_rep, fichier))
l_resultats = resultats.split(":")
remplissage[0] += float(l_resultats[0])*float(l_resultats[1])
remplissage[1] += float(l_resultats[1])
total_noir = remplissage[0]
faces = int(remplissage[1])
if (fic_impr.recto_verso == False):
pages = faces # nb de pages par copies
else:
pages = int(faces/2.+0.5)
c_total = (c_taille * pages +
impression.c_tambour_noir * faces+cout_noir * total_noir)
# Cout des agrafes
if pages <= 50:
c_agrafes = fic_impr.nb_copies * (impression.c_agrafe * abs(fic_impr.agrafe))
else:
c_agrafes = 0
c_total = int(fic_impr.nb_copies * c_total + impression.fact +
c_agrafes +
0.5) # arrondi et facture
if commands.getoutput("rm -r -f %s" % nom_rep):
self.list_messages_admin.append('erreur_cout')
sys.stderr.write("ERREUR : Impossible d'enlever le dossier.\n")
return float(c_total)/100