Imprime

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) :
- snapshot 3 du 11/12/2008 ;
- snapshot 2 du 11/12/2008 ;
- snapshot 1 du 11/12/2008 ;
- snapshot du 15/11/2008 ;
- snapshot du 12/10/2008 ;
- snapshot du 08/10/2008 ;
- snapshot du 06/10/2008 ;
- deuxième snapshot du 05/10/2008 ;
- snapshot du 05/10/2008 ;
- snapshot du 04/10/2008.
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%