#! /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.replace("\"","\\\"") # Pour donner le login de l'adherent options += ' -U \"%s\"' % self.user.replace("\"","\\\"") # 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: 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 "

status:%d rep: %s

" % (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 "

%s

" % 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 '

ligne non parsée: %s

' % 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 = "\n\n" % (self.nom_job, time.ctime(0.0)) corps += "\n\n
%s%s
\n\n" self.code = "%s#" % self.code for key in ("nbr_pages", "nb_copies", "taille", "code"): corps += '\n' % (key, str(getattr(self,key))) self.code = self.code[:-1] corps += "
%s%s
\n
\n\n" corps += '\n' % dict_agrafe[self.agrafe] for key in ("couleur", "transparent", "portrait", "recto_verso"): if getattr(self, key): corps += '\n' % dict_normale[key] else: corps += '\n' % dict_contraire[key] corps += "
%s
%s
%s
\n
" if self.erreur_critique: corps += "erreur de comptage" else: corps += "%s euros" % self.cout corps += "
" 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