Imprime

De Ensiwiki
Aller à : navigation, rechercher
AttentionCette page est laissée pour mémoire, mais la commande n'existe plus et n'a plus lieu d'être avec le nouveau système d'impression


imprime était une commande spécifique à l'Ensimag, destinée a faciliter les opérations d'impression, et d'éviter le gaspillage de papier.

bash-3.2$ imprime monDocument.pdf

imprimer, un remplaçant pour imprime

imprimer est le successeur d'imprime, entièrement récrit par mes (len) soins, avec l'aide de Gugli, en shell (avec beaucoup d'awk). Il reprend les grandes idées d'imprime : permettre à tous d'imprimer facilement les types de documents les plus courants (PDF, PS, code, texte, etc.), tout en essayant de limiter le gaspillage et les bouchons dans les files d'impression.

Avancement, état du développement, ou pourquoi on n'arrive pas à une solution satisfaisante

Actuellement, je n'ai ni l'envie (et ce n'est pas uniquement un problème technique...) ni le temps de continuer à développer ce script, au vu du manque apparent de solution, de documentation, et d'intérêt associés au projet. Le code est franchement utilisable mais largement imparfait ; avis aux amateurs, le dépôt Git ne va pas mourir demain, n'hésitez pas à envoyer un mail...

Bon, comme ça stagne, va falloir expliquer pourquoi. La raison simple c'est qu'en fait les heuristiques ne donnent pas du tout des résultats intéressants, en clair : je (Len) commence à ne plus rien comprendre au bordel du parc d'imprimantes (voyez ci-dessous).

Ce qui est su du parc d'imprimantes

Ci-dessous je retranscris ce que je sais sur tout ça, histoire de laisser une trace.

Concernant les files d'impression :

  • Il y a un serveur d'impression (CUPS) par machine utilisateur, et chacun gère ses propres files d'impression, invisibles les unes des autres.
  • Il est possible d'envoyer des tâches directement à l'imprimante, dans quel cas on se retrouve dans la file de l'imprimante mais aucune des files CUPS.
  • Les files CUPS ne reflètent pas la réalité : en période de pointe, il y a des jobs qui sont dans les files et qui n'arrivent jamais à destination sur l'imprimante ; d'autres jobs sont traités dans le désordre par l'imprimante ; d'autres encore obtiennent la priorité quoiqu'ils sont lancés après et sans rien de spécial (résultat empirique). J'en (Len) déduis que les files CUPS sont (en partie ?) gérées en soft par CUPS lui-même et ne sont pas synchronisés avec l'imprimante.
  • Lorsqu'un job plus récent s'imprime, cela signifie en général que les autres sont morts et n'arriveront jamais à destination (résultat empirique).
  • Parfois, la file de jobs est entièrement vidée pour une imprimante donnée (existence d'un panic mode ?).

Concernant l'état des imprimantes :

  • Il n'y a pas de moyen simple d'obtenir le niveau d'encre ou de papier (le seul moyen qui a été trouvé pour l'instant est de parse le code HTML de l'interface Web) ; l'interface CUPS console n'a rien à cet effet, les interfaces telnet et FTP (sic) des imprimantes ont été testées sans succès.
  • Il y a des pannes qu'il est semble-t-il impossible de détecter.
  • Le job en cours rapporté par CUPS n'est pas le bon dans bien des cas.

Concernant le formattage avant impression (là c'est presque bon) :

  • CUPS ne prend que du PS en natif, même si l'imprimante supporte autre chose.
  • On a mpage(1) pour mettre plusieurs pages logiques sur une page physique, mais pas moyen d'enlever les bordures (quoi qu'en dise le man).

Fonctionnalités

  • Sélection automatique de l'imprimante en fonction :
    • des préférences de l'utilisateur ;
    • de la charge des machines ;
    • et de leur état.
  • Sélection manuelle de l'imprimante :
    • sélection totalement manuelle ;
    • de listes d'exclusions.
  • Gestion des formats de fichiers :
    • sélection automatique d'une commande ;
    • gestion uniforme des options d'impression ;
    • gestion des fichiers intermédiaires ;
    • gestion des formats interdits.
  • Gestion des travaux en cours d'impression :
    • détection des impressions redondantes ;
    • annulation des travaux en cours en cas de redondance.

Ce qu'il reste à faire

Des tests !

Cela mis à part, l'algorithme de sélection de l'imprimante est perfectible ; on pourrait l'étendre de manière déterministe, en essayant de récupérer les informations des autres serveurs CUPS (voir page de discussions), ou de manière probabiliste en choisissant une imprimante de manière aléatoire, avec une certaine pondération (rapidité, etc.). Plus de détails dans la page de discussions.

Documentation

Vous trouverez la doc interne (sur comment imprimer est fait) dans les sources, dans le dossier doc, en attendant que les sources soient réfléchie sur les serveurs de l'Ensimag ; vous pourrez alors y accéder par :

 /imag/src/imprimer/doc/

Enfin, si tout se passe bien !

Sources

Dépôt Git :

 git://git.huoc.org/imag/imprimer.git

Git Web :

 http://git.huoc.org/?p=imag/imprimer.git;a=summary

Anciens snapshots (avant de passer à Git) :

Code de l'ancienne commande imprime

Voici le code du script. Si certains d'entre vous ont des idées de modifications, qu'ils n'hésitent pas : dès qu'un root le verra, il pourra valider (verifier qu'il n'y a rien de frauduleux) et uploader la nouvelle version pour la rendre disponible à tous.

#!/bin/bash

#################################################
#                                               #
# Script d'impression simplifiée et Anti-Gaspi  #
#                                               #
#################################################


# Fait par Sylvain "Gugli" Guglielmi
# Etudiant IMAG 2A,
# Projet commencé le 12 Octobre 2007

#Pourquoi un script shell ?
#  Pour etre sur qu'il puisse servir a nouveau
#  quels que soient les architectures des serveurs
#  sans avoir à être recompile
#  (mais aussi pour le l337 f4ct0r)

# Si quelqu'un venait à vouloir retravailler ce script
#  je pense être joignable à sylvain.guglielmi@ensimag.fr

# Merci à ceux qui s'interresseront aux "tripes" de
# ce script,

# Tant mieux si du monde l'utilise encore
#    tant pis si ça tombe dans l'oubli...

#################################################
#                  Constantes                   #
#################################################

# Nombre de travaux max par utililsateur sur
# toutes les imprimantes réunies
nbr_travaux_max_par_user=2

# Fichier contenant les different formats et les
# Nombre de travaux max par utililsateur sur
# commandes a employer correspondantes.
# Le format du fichier est décrit en en-tête
formats="$( sed '/^\/\//d' < '/imag/bin/formats.list' )"

# Fichier contenant les correspondances
# Entre les noms d'imprimantes et les etages
etages="$( sed '/^\/\//d' < '/imag/bin/etages.list' )"

#################################################
#         Recuperation des parametres           #
#################################################

# Nom de l'utilisateur
    user=$(whoami)
    
# Travaux en attente pour l'utilisateur
    travaux=$(lpstat)
    
# Nombre de travaux en attente pour l'utilisateur
    nbr_travaux=$(echo -n "$travaux"|wc -l)

# Tabeau des argments
  for i in $(seq 1 $#)
  do
    parametres[$i]="${!i}"

    # tableau de l'état du parametre
    # n = pas encore pars
    # p = paramètre
    # f = fichier valide
    # i = fichier invalide ?
    param_pars[$i]='n'
  done

#Fonction qui extrait un parametre de la liste
trouve_param() #[marque du parametre] [valeur par defaut]
{   
    trouve='faux'
    for i in $(seq 1 ${#parametres[*]} )
      do
      if [[ "${parametres[$i]}" = "$1" ]]
          then 
          {
              dernier_parametre="${parametres[$(($i+1))]}"
              param_pars[$i]='p'
              param_pars[$(($i+1))]='p'
              trouve='vrai'
          }
      else
          {
              if [[ -n $(echo "${parametres[$i]}" | grep -e "$1") ]]
                  then
                  {
                      dernier_parametre=$(echo "${parametres[$i]}"|sed "s/.*$1//");
                      param_pars[$i]='p'
                      trouve='vrai'
                  }
              fi
          }
      fi
    done
 
    if [[ "$trouve" = "faux" ]]
        then
        {
            #Valeur par defaut
            dernier_parametre="$2"
        }
    fi  
}

trouve_option() #[marque du parametre]
{   

    derniere_option='faux'
    for i in $(seq 1 ${#parametres[*]} )
      do
      if [[ -n $(echo "${parametres[$i]}" | grep -e "$1") ]]
          then
          {           
              param_pars[$i]='p'
              derniere_option='vrai'
          }
      fi              
    done    
}

trouver_parametres()
{ 
   
# Nombre de copies
    trouve_param -# 1
    nombre_copies=$dernier_parametre
    #echo "$nombre_copies" "copies"

# etage_prefere
    trouve_param -e 1
    etage_prefere=$dernier_parametre
    #echo "Etage prefere : " "$etage_prefere"

    #Selection des imprimantes de l'étage
    imprimantes_preferees=( $( echo "$etages" | awk -F':' "\$3 == $etage_prefere{print \$2}" ) )


# imprimantes_preferees
    trouve_param -P "aucune"
    if [[ "$dernier_parametre" != "aucune" ]]
        then
        unset imprimantes_preferees
        imprimantes_preferees[0]=$dernier_parametre
    fi
    #echo "imprimante : " "${imprimantes_preferees[@]}" "a l'etage" "$etage_prefere"

# RectoVerso
    trouve_option --recto-verso ""
    trouve_option -r "$derniere_option"
    recto_verso=$derniere_option
    #echo "Recto-verso : " "$recto_verso"

# Format
    trouve_param -A 5
    format_papier=$dernier_parametre
    #echo "Format papier : " "$format_papier"

    case "$format_papier" in
        "4") 
            format_lpr=""
            format_a2ps=" -1B --borders=no --margin=3 "
            ;;
        "5") 
            format_lpr=""
            format_a2ps=" -2B --borders=no --margin=3"
            ;;
        *)
            echo "Format d'impression non reconnu : doit etre A4 ou A5"
            ;;
    esac 
    
}

#################################################
# Tests des fichiers à imprimer

test_fichiers()
{
    fichier_valide='faux'
    for i in $(seq 1 ${#parametres[*]} )
      do

      if [[ ${param_pars[$i]} = 'n' ]]
          then
          {   

              # Tous les arguments qui ne sont pas des parametres reconus sont des fichiers     
              if [[ -e "${parametres[$i]}" ]]
                  then
                  {                                       
                      param_pars[$i]='f'
                      fichier_valide='vrai'
                  }
              else
                  {  
                      #Fichier introuvable
                      echo "Le fichier ${parametres[$i]} n'existe pas"
                      param_pars[$i]='i'
                  }               
              fi
          }
      fi
    done

    if [[ "$fichier_valide" = 'faux' ]]
        then
        {
            # Aucun fichier n'a ete specifie
            echo "Usage : imprime [Fichier_a_imprimer]"
            rien_imprime="vrai"
        }
    fi
}



#################################################
#                     Tests                     #
#################################################

menu()
{
        echo " "
        echo -n "[0-$1]?"

        reponse="a"
        while [[ -n $(echo "$reponse"| sed "s/[0-$1]//") ]]
        do
        {
                read -s -n 1 reponse
        }
        done
        echo "$reponse"
        echo ""
}


#################################################
# Si l'utilisateur a trop de travaux

test_utilisateur()
{
    if [[ -n "$rien_imprime" ]]
        then
        {
            return 0;
        }
    fi

    if [[ $nbr_travaux -gt $nbr_travaux_max_par_user ]]
        then
        {
            echo "Vous avez plus de documents en attente d'impression que le max authorise"
            echo 'En cours :' "$nbr_travaux" ' // Authorises :' "$nbr_travaux_max_par_user"
            echo "Voulez-vous annuler un travail precedent :"
            echo " "

            stats=$(lpstat)
            echo "0)-> Ne pas lancer de nouvelle impression"
            for (( i=1 ; i<=$nbr_travaux ; i+=1 ))
              do
              lignes_travaux[$i]=$(echo "$stats"|head -n $(($i+2))|tail -n 1)
              echo "$i)->" ${lignes_travaux[$i]}
            done
            
            menu "$nbr_travaux"

            if [[ $reponse -ne 0 ]]
                then
                {
                    lprm $( echo "${lignes_travaux[$reponse]}"| sed 's#.*-##' | sed 's# .*##' )
                }
            else
                {
                    rien_imprime="vrai"
                }
            fi

        }
    fi
}
#################################################
# Choix de l'imprimante 

choix_imprimante()
{
    #On choisit l'imprimante la plus adaptee

    if [[ -n "$rien_imprime" ]]
        then
        {
            return 0;
        }
    fi

    #On essaie de trouver une imprimante libre parmi celles choisies
    stats=$(lpstat -p)
    for i in $(seq 0 $(( ${#imprimantes_preferees[*]} -1 )) )
      do
      {
          if [[ -n $(echo "$stats" | grep "${imprimantes_preferees[$i]}" |grep 'idle' ) ]]
              then
              {
                 # La file d'attente de l'imprimante est vide
                 imprimante="${imprimantes_preferees[$i]}"
                 return 0;                
              }
          fi          
      }
    done    

    echo "L'imprimante est deja occupee"
    echo "Voulez vous imprimer ailleurs :"


    for i in $(seq 0 $(( $( echo "$etages" | wc -l) -1 )) )
      do
      {
          # Nombre de travaux sur chaque imprimante
          nb_hp[$i]=$(lpstat -o $( echo "$etages" | awk -F':' "\$1==$i{print \$2}" ) |wc -l)

          echo -n "$i)->Travaux en cours sur "
          imp=$( echo "$etages" |awk -F":" "\$1 == $i {print \$2 \" (etage \" \$3 \")\"}" )
          echo -n "$imp"
          echo " : ${nb_hp[$i]}";
      }
    done
    
    menu 4
    imprimante=$( echo "$etages" | awk -F":" "\$1 == $reponse {print $2}" )    
}

#################################################
# Impression d'un fichier

impression_1() # [Nom_du_fichier]
{

    # Detection de l'extention du fichier
    if [[ -n $(basename  "$1" | grep '\.' ) ]]
        then
        {
            extention=$(basename "$1"|sed 's_.*\.__')
        }
    else
        {
            echo "Pas d'extention, impression de $1 au format texte"
            extention="txt"
        }
    fi

    # Choix de la commande en fonction du format
    commande=$( echo "$formats" | grep " $extention " | sed 's/.*://' )


    commande=$( echo "$commande"| sed "s#%adr_imprimante%#$imprimante#g" )
    commande=$( echo "$commande"| sed "s#%fichier%#\"$1\"#g" )
    commande=$( echo "$commande"| sed "s#%nombre_copies%#$nombre_copies#g" )
    commande=$( echo "$commande"| sed "s#%format_lpr%#$format_lpr#g" )
    commande=$( echo "$commande"| sed "s#%format_a2ps%#$format_a2ps#g" )
  
}

#################################################
# Impression d'un fichiere tous les fichiers

impression()
{

###########################################################
# Logging des infos
# C'est un peu big brother, mais ça me permet d'avoir un feedback
# sur les formats les plus demandés ....

    #echo "***************************" >> /tmp/imprime.log
    #echo "$date" >> /tmp/imprime.log
    #echo "$@" >> /tmp/imprime.log
###########################################################

    if [[ -n $rien_imprime ]]
        then
        {
            echo "Rien n'a ete imprime"
            return 0;
        }
    fi

    imprime='faux'
    for i in $(seq 1 ${#parametres[*]} )
      do
      if [[  ${param_pars[$i]}='f' ]]
          then
          {
              impression_1 "${parametres[$i]}"

              if [[ -z "$commande" ]]
                  then
                  {
                      # Format (pas encore) géré
                      echo "Le format du fichier ${parametres[$i]} n'est pas reconnu"
                  }
              else
                  {
                      echo "Impression de ${parametres[$i]}"
                      echo  "commande :" $commande
                      $commande
                      #echo "$commande" >> /home/ensi2a/gugliels/share/imprime.log
                      imprime='vrai'
                  }
              fi

          }
      fi              
    done  
    
    if [[ "$imprime" = 'vrai' ]]
        then
        {
            echo " "
            echo "Impression lancee avec succes sur :"
            echo -n "$imprimante" "a l'etage" 
            echo $(echo $etages | awk -F':' "\$2 == $imprimante {print \$3 }" )
        }
    fi
}


#################################################
#            Lancement des fonctions            #
#################################################

trouver_parametres

test_fichiers

test_utilisateur

choix_imprimante

impression


Annexes

Le fichier etages.list

// numero arbitraire : nom : etage
3:hp3:1
1:hp1:1
2:hp2:2
4:hp4:2
0:hp:2

Le fichier formats.list

// Fichier des extentions pour le script d'impression
// /!\ La première ligne est exucutée si le fichier 
//       n'a pas d'extention
// Variables : 
//      %adr_imprimante%
//      %fichier%
//
//
//
jpg JPG gif GIF png PNG :convert %fichier% %fichier%.ps; lpr %fichier%.ps
ps txt PS TXT :lpr %format_lpr% -# %nombre_copies% -P %adr_imprimante% %fichier%
pdf PDF :a2ps -q %format_a2ps% -n %nombre_copies% -P %adr_imprimante% %fichier%
cpp hpp c h CPP HPP C H :a2ps -q %format_a2ps% --line-numbers=1 -n %nombre_copies% -P %adr_imprimante% %fichier%