#!/usr/bin/env python # -*- coding: utf-8 -*- # ############################################################# # .. # .... ............ ........ # . ....... . .... .. # . ... .. .. .. .. ..... . .. # .. .. ....@@@. .. . ........ . # .. . .. ..@.@@..@@. .@@@@@@@ @@@@@@. .... # .@@@@. .@@@@. .@@@@..@@.@@..@@@..@@@..@@@@.... .... # @@@@... .@@@.. @@ @@ .@..@@..@@...@@@. .@@@@@. .. # .@@@.. . @@@. @@.@@..@@.@@..@@@ @@ .@@@@@@.. ..... # ...@@@.... @@@ .@@.......... ........ ..... .. # . ..@@@@.. . .@@@@. .. ....... . ............. # . .. .... .. .. . ... .... # . . .... ............. .. ... # .. .. ... ........ ... ... # ................................ # # ############################################################# # impression_canon.py # # Classe impression pour l'imprimante canon iR C3580i # # Copyright (c) 2006, 2007, 2008, 2009 by Cr@ns (http://www.crans.org) # ############################################################# """ Classe pour gérer l'envoie de pdf à l'imprimante. Calcule le coût des options d'impression. """ __version__ = '9.11' import sys, os.path sys.path.append('/usr/scripts/gestion') from config import impression as config_impression from commands import getstatusoutput import crans.utils.logs from subprocess import Popen, PIPE # ######################################################## # # CONSTANTES # # ######################################################## # FICHIER_LOG = "/var/log/log_couts/impressions" SNMP_CAPA_B = "mib-2.43.11.1.1.8.1.1" SNMP_CAPA_C = "mib-2.43.11.1.1.8.1.2" SNMP_CAPA_M = "mib-2.43.11.1.1.8.1.3" SNMP_CAPA_Y = "mib-2.43.11.1.1.8.1.4" SNMP_TON_B = "mib-2.43.11.1.1.9.1.1" SNMP_TON_C = "mib-2.43.11.1.1.9.1.2" SNMP_TON_M = "mib-2.43.11.1.1.9.1.3" SNMP_TON_Y = "mib-2.43.11.1.1.9.1.4" SNMP_BAC1 = "mib-2.43.8.2.1.10.1.2" SNMP_BAC2 = "mib-2.43.8.2.1.10.1.3" SNMP_BAC3 = "mib-2.43.8.2.1.10.1.4" SNMP_BAC4 = "mib-2.43.8.2.1.10.1.5" SNMP_COUNT_A4 = "enterprises.1602.1.11.1.4.1.4.113" SNMP_COUNT_A3 = "enterprises.1602.1.11.1.4.1.4.112" SNMP_COUNT_A4c = "enterprises.1602.1.11.1.4.1.4.123" SNMP_COUNT_A3c = "enterprises.1602.1.11.1.4.1.4.122" SNMP_COUNT_TOT = "enterprises.1602.1.11.1.4.1.4.101" SNMP_ETAT = "hrPrinterStatus.1" SNMP_ERR = "hrPrinterDetectedErrorState.1" DECOUVERT_AUTHORISE = config_impression.decouvert DICT_AGRAFAGE = { "None" : "aucune agrafe", "TopLeft" : u"agrafe en haut à gauche", "TopRight" : u"agrafe en haut à droite", "BottomLeft" : u"agrafe en bas à gauche", "BottomRight" : u"agrafe en bas à droite", "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", "Bottom" : u"deux agrafes sur le bord inférieur" } AVAIL_AGRAFES = ["None", "TopLeft", "TopRight", "Left", "BottomLeft", "BottomRight", "Right"] DICT_PAPIER = { 'A4' : "Papier A4 ordinaire", 'A3' : "Papier A3 ordinaire", 'A4tr' : "Transparent A4" } # ######################################################## # # ERREURS # # ######################################################## # # class FichierInvalide(Exception): """ Exception renvoyée lorsqu'un fichier ne passe pas. utilisée avec deux arguments : une chaîne décrivant l'erreur et une chaîne avec le nom du fichier """ def __str__(self): """ Description de l'erreur. """ return self.args[0] def file(self): """ Nom du fichier qui pose problème """ try: return self.args[1] except: return "n/a" class SoldeInsuffisant(Exception): """ Solde insuffisant pour l'impression demandée """ pass class PrintError(Exception): """ Erreur lors de l'impression """ pass class SettingsError(Exception): """ Erreur de paramètres. """ pass def _uniq_jid(): """ 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 # ######################################################## # # CLASSE IMPRESSION # # ######################################################## # # # class impression: """impression Un objet impression correspond à un fichier pdf et un adhérent. """ # fichier (chemin) _fichier = "" # adherent (instance) _adh = None # paramettres _settings = { 'agrafage': 'None', 'papier': 'A4', 'couleur': False, 'recto_verso': False, 'livret': False, 'copies': 1, 'portrait': True, } # 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 ouvre les logs self.log = crans.utils.logs.getFileLogger('impression') 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)",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)']: raise FichierInvalide, u"Seuls les formats A3 et A4 sont supportes" # calcule le prix de l'encre tout de suite self._calcule_prix() def _pdfbook(self): page = "pdfinfo \"%s\" | grep Pages | awk '{print $2}'" % self._fichier (status, npage) = getstatusoutput(page) if status != 0: self.log.error("pdfinfo status:%d | rep: %s" % (status, npage)) raise FichierInvalide, ("pdfinfo: Impossible de trouver le nombre de page du fichier", self._fichier) if int(int(npage)/4*4) < int(npage): sig=int((int(npage)/4 +1)*4) else: sig=int(npage) if self._settings['papier'] == 'A3': newfile = self._fichier[:-4] + '-a3book.pdf' pdfbook = "pdfbook --signature %s --paper a3paper %%s --outfile %s" % (sig,newfile) else: newfile = self._fichier[:-4] + '-book.pdf' pdfbook = "pdfbook --signature %s %%s --outfile %s" % (sig,newfile) (status, rep) = getstatusoutput(pdfbook % self._fichier) self.log.info("%s | rep: %s" % ((pdfbook % self._fichier), rep)) self._fichier = newfile if status != 0: self.log.error("pdfbook status:%d | rep: %s" % (status, rep)) raise FichierInvalide, ("pdfbook: Impossible de convertir le fichier", self._fichier) def changeSettings(self, **kw): """changeSettings([keyword=value]) Change les parametres de l'impression, recalcule et renvoie le nouveau prix. Lève une exceotion SettingError si les paramètres son invalides. """ #recalcule et renvoie le prix couleur = kw.get('couleur', None) if couleur in [True, False]: self._settings['couleur'] = couleur elif couleur == "True": self._settings['couleur'] = True elif couleur == "False": self._settings['couleur'] = False try: if int(kw['copies']) >= 1: self._settings['copies'] = int(kw['copies']) except: pass recto_verso = kw.get('recto_verso', None) if recto_verso == "True": recto_verso = True if recto_verso == "False": recto_verso = False if recto_verso in [True, False]: self._settings['recto_verso'] = recto_verso papier = kw.get('papier', None) if papier in ['A4', 'A3', 'A4tr']: self._settings['papier'] = papier if papier == 'A4tr': self._settings['recto_verso'] = False self._settings['agrafage'] = 'None' agrafage = kw.get('agrafage', None) if agrafage in ["None", "TopLeft", "Top", "TopRight", "Left", "Right", "BottomLeft", "BottomRight"]: self._settings['agrafage'] = agrafage livret = kw.get('livret', None) if livret == "True": livret = True if livret == "False": livret = False if livret in [True, False]: self._settings['livret'] = livret if livret: self._settings['portrait'] = True #Le mode paysage est géré par _pdfbook self._settings['recto_verso'] = True self._settings['agrafage'] = 'None' if self._settings['papier'] == 'A4tr': self._settings['papier'] = 'A4' else: self._settings['portrait'] = self._width < self._height return self._calcule_prix() def printSettings(self): """printSettings() Affiche les paramètres courrants sur la sortie standard """ if self._settings['couleur']: print "Type impression: Couleur" else: print "Type impression: Noir et blanc" print "Papier: " + DICT_PAPIER[self._settings['papier']] if self._settings['livret']: print u"Agrafage: Livret (piqûre à cheval)" else: print "Agrafage: " + DICT_AGRAFAGE[self._settings['agrafage']] if self._settings['recto_verso']: print "Disposition: recto/verso" else: print "Disposition: recto" print "Copies: " + str(self._settings['copies']) 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 = _uniq_jid() # debite l'adhérent si adherent il y a if (self._adh != None): adh = self._get_adh(self._adh) if (self._prix > (adh.solde() - DECOUVERT_AUTHORISE)): raise SoldeInsuffisant adh.solde(-self._prix, "impression(%d): %s" % (self._jid,self._fichier)) adh.save() del adh # imprime le document self._exec_imprime() def _calcule_prix(self): faces = self._pages if self._settings['livret']: feuilles = int((faces+3)/4) faces = 2 * feuilles elif self._settings['recto_verso']: feuilles = int(faces/2.+0.5) else: feuilles = faces if (self._settings['papier'] == "A3"): c_papier = config_impression.c_a3 pages = 2*faces else: pages = faces if self._settings['papier'] == "A4tr": c_papier = config_impression.c_trans else: c_papier = config_impression.c_a4 if self._settings['couleur']: 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 if self._settings['agrafage'] in ["Top", "Bottom", "Left", "Right"] or self._settings['livret']: nb_agrafes = 2 elif self._settings['agrafage'] in ["None", None]: nb_agrafes = 0 else: nb_agrafes = 1 if feuilles <= 50: c_agrafes = nb_agrafes * config_impression.c_agrafe else: c_agrafes = 0 c_total = int(self._settings['copies'] * ( 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: sys.path.append("/usr/scripts/gestion/") #from ldap_crans_test import crans_ldap from 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._settings['livret']: self._pdfbook() if (self._adh != None): self.log.info('Impression(%d) [%s] : %s' % (self._jid, self._adh, self._fichier)) else: self.log.info("Impression(%d) : %s" % (self._jid, self._fichier)) # 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 if self._adh <> 'passoir': options += ' -P canon_irc3580' else: options += ' -P canon_test' # 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("\"","\\\"")) # Ce nom apparaît sur l'interface d'impression de l'imprimante: options += " -o CNDocName=\"%s\"" %jobname # Et dans lpq: options += " -T \"%s\"" % jobname # Pour donner le login de l'adherent options += ' -U \"%s\"' % self._adh # Pour spécifier la version du language postscript utilisé par pdftops # options += ' -o pdf-level3' # 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._settings['papier'] == 'A4tr': options += ' -o InputSlot=SideDeck -o MediaType=OHP' options += ' -o PageSize=A4' elif self._settings['papier'] == 'A4': options += ' -o PageSize=A4' else: options += ' -o pdf-expand -o pdf-paper=841x1190 -o PageSize=A3' if self._settings['portrait']: if self._settings['recto_verso']: options += ' -o sides=two-sided-long-edge' else: options += ' -o sides=one-sided' else: if self._settings['recto_verso']: options += ' -o sides=two-sided-short-edge -o landscape' else: options += ' -o sides=one-sided -o landscape' if self._settings['couleur']: options += ' -o CNColorMode=color' else: options += ' -o CNColorMode=mono' if self._settings['livret']: options += ' -o CNSaddleStitch=True' options += ' -o OutputBin=TrayC' else: options += ' -o OutputBin=TrayA' options += ' -o Collate=StapleCollate -o StapleLocation=%s' % self._settings['agrafage'] if not self._settings['livret'] and self._settings['agrafage'] in ['None', None]: left = self._settings['copies'] while left >= 100: cmd = "lpr %s -# %d %s" % (options, 99, self._fichier) left -= 99 (status, rep) = getstatusoutput(cmd) self.log.info("printing: %s" % cmd) if status != 0: self.log.error("erreur impression") self.log.error("lpr status:%d | rep: %s" % (status, rep)) raise PrintError, "%s \n status:%d rep: %s" % (cmd, status, rep) cmd = "lpr %s -# %d %s" % (options, left, self._fichier) (status, rep) = getstatusoutput(cmd) self.log.info("printing: %s" % cmd) if status != 0: self.log.error("erreur impression") self.log.error("lpr status:%d | rep: %s" % (status, rep)) raise PrintError, "%s \n status:%d rep: %s" % (cmd, status, rep) else: cmd = "lpr %s %s" % (options, self._fichier) self.log.info("printing [%s]: %s" % (cmd, self._settings['copies'])) for i in range(self._settings['copies']): (status, rep) = getstatusoutput(cmd) if status != 0: self.log.error("erreur impression") self.log.error("lpr status:%d | rep: %s" % (status, rep)) raise PrintError, "%s \n status:%d rep: %s" % (cmd, status, rep)