#!/usr/bin/python -tt
# -*- coding: utf-8 -*-
# Project:	update-fotos-ldap
# Module:	actualizafotoldap.py
# Purpose:	Actualiza fotos de alumnos en el directorio LDAP desde datos de Rayuela 
# Language:	Python 2.5
# Date:		03-Feb-2011.
# Ver:		07-Feb-2011.
# Author:	Francisco Mora Sánchez
# Copyright:	2011 - Francisco Mora Sánchez <adminies.maestrojuancalero@edu.juntaextremadura.net>
#
# update-fotos-ldap is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# update-fotos-ldap is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with update-fotos-ldap. If not, see <http://www.gnu.org/licenses/>.

""" Programa para actualizar las fotos de los alumnos
en el servidor ldap a partir del fichero de alumnos generado por
Rayuela. Tambien depura las entradas del grupo students, eliminando
aquellas membresias cuyos uids no están en la rama People, si lo queremos.

Necesita paquetes instalados python python-ldap python-imaging

En los servidores de terminales está todo instalado excepto python-ldap
que se carga ejecutando la orden como superusuario:

apt-get install python-ldap

Uso: crearemos una carpeta donde copiaremos este programa y además 
pondremos el ZIP generado por Rayuela, denominado
ExportacionDatosAlumnado.zip, ejecutaremos el script pasando como 
parámetros como mínimo la contraseña del administrador ldap, por defecto
el programa establece el URI ldap de nuesto servidor ldap y la ruta a la 
carpeta con el ZIP de rayuela en la propia carpeta.

Existe un parámetro, denominado --depurastudents {on|off} el cual si lo 
establecemos a on, eliminará del grupos students de ldap aquellas entradas
que no tienen un uid en el grupo People, normalmente son residuos de
uids de alumnos que ya no están en el centro.

OJO: Realizar copias de seguridad de ldap antes de ejecutar el script...


Francisco Mora Sánchez
IES Maestro Juan Calero
adminies.maestrojuancalero@edu.juntaextremadura.net


"""

import sys,os,shutil,zipfile,ldap,Image,glob,time,copy
import ldap.modlist as modlist

# Set debugging level
#ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
#ldap.set_option(ldap.OPT_DEBUG_LEVEL,0)
#ldapmodule_trace_level = 1
#ldapmodule_trace_level = 0
#ldapmodule_trace_file = sys.stderr

def descomprime_zip_en_directorio(fichero, carpeta):
	"""Funcion que dado un fichero zip lo descomprime en la
	carpeta indicada."""
	if os.path.exists(carpeta):
		shutil.rmtree(carpeta)
	os.mkdir(carpeta, 0777)
	zfobj = zipfile.ZipFile(fichero)
	for name in zfobj.namelist():
		if name.endswith('/'):
			os.mkdir(os.path.join(carpeta, name))
		else:
			outfile = open(os.path.join(carpeta, name), 'wb')
			outfile.write(zfobj.read(name))
			outfile.close()
			
def lee_parametros(lista,params):
	"""Funcion que crea un diccionario para leer el valor de los 
	parametros que se pasan en la linea de comandos."""
	for i in range(1,len(lista)):
		if lista[i][:2] == "--":
			#veamos si tiene valor
			if i+1 < len(lista) and lista[i+1] <> "--":
				#estamos en un parametro y valor
				params[lista[i][2:]] = lista[i+1]

def conecta_a_ldap(uri,contra):
	""" Conexión al servidor ldap
	"""
	try:
		usuario_admin = "cn=admin,ou=People,dc=instituto,dc=extremadura,dc=es"
		#conexion_ldap = ldap.initialize(uri,trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file)
		conexion_ldap = ldap.initialize(uri)
		conexion_ldap.protocol_version=ldap.VERSION3
		conexion_ldap.bind_s(usuario_admin, contra)
	except ldap.LDAPError, e:
		return None
	
	return conexion_ldap

def desconecta_de_ldap(conexion_ldap):
	""" Desconectar del servidor ldap
	"""
	conexion_ldap.unbind_s()

def buscar_en_ldap(conexion_ldap, filtro, atributos, baseDN = "ou=People,dc=instituto,dc=extremadura,dc=es", alcance = ldap.SCOPE_SUBTREE):
	## Realiza una consulta al servidor ldap, utilizando un filtro y delvolviendo unos atributos, la salida es una lista de tuplas
	try:
		return conexion_ldap.search_s(baseDN, alcance, filtro, atributos)
	except:
		pass
	else:
		return []

def existeArchivoImagen(archivo):
	""" Funcion que determina si existe un archivo 
	"""
	return os.path.exists(archivo)

def main():
	params = {}
	lista = sys.argv
	lee_parametros(lista,params)
	#comprobaremos las combinaciones de parametros en linea de comandos
	if not params.has_key("contra"):
		print 'uso: ./actualizafotosldap.py --contra contraseña_ldap [--depurastudents {on,off}] [--urildap ldaps://servidor_ldap:puerto] [--ruta rutaalarchivozipderayuela]'
		sys.exit(1)
	if not params.has_key("urildap"):
		print "No se ha establecido el servidor ldap, usando por defecto 'ldaps://ldap:636'"
		params["urildap"] = "ldaps://ldap:636"
	if not params.has_key("ruta"):
		print "No se ha establecido la ruta del archivo, usando por defecto './ExportacionDatosAlumnado.zip'"
		params["ruta"] = "./ExportacionDatosAlumnado.zip"
	if not params.has_key("depurastudents"):
		print "No se ha establecido depurastudents, no se eliminarán alumnos del grupo students que no existan en la rama People"
		params["depurastudents"] = "off"
	
	#Emite mensajes
	print "Los valores pasado son los siguientes:"
	print "URI Servidor ldap: ",params["urildap"]
	print "Ruta al archivo: ",params["ruta"]
	print "Eliminar alumn@s del grupo students no existentes en People:",params["depurastudents"]
	print "Si estás conforme, pulsa intro, sino pulsa CTRL+C y aborta la ejecución"
	raw_input()
	#Comprobemos la existencia del archivo
	if not os.path.exists(params["ruta"]):
		print params['ruta']," no existe"
		exit(2)
	print "Intentando la conexión al URI de ldap '"+params["urildap"]+"'"

	conexion_ldap = conecta_a_ldap(params["urildap"],params["contra"])
		
	if conexion_ldap == None:
		print "Error en la conexión al servidor ldap:",params["urildap"],"revise nombre servidor y/o contraseña"
		exit(3)
	
	#busqueda de los alumnos(pertenecen al grupo students
	resultado_busqueda_alumnos = buscar_en_ldap(conexion_ldap, 'member=*', ['member','memberUid'],baseDN="cn=students,ou=Group,dc=instituto,dc=extremadura,dc=es")

	if resultado_busqueda_alumnos is None:
		print "No existen alumnos que pertenezcan al grupo students..."
		exit(4)

	print "Descomprimiendo ZIP de alumnos de rayuela en la carpeta actual..."
	descomprime_zip_en_directorio(params["ruta"], "temp")

			
	alumnos = resultado_busqueda_alumnos[0][1]["memberUid"]
	encontrados = 0
	alumnos_a_borrar_grupo_students = []
	print "Total de alumnos encontrados",len(alumnos)
	print "Procesando alumnos..."
	separador = os.sep
	tipos_imagen = ['.jpg', '.png', '.jpeg' , '.bmp', '.tiff','.jpg']
	alumnos_sin_foto = []
	for alumno in alumnos:
		datos_alumno = buscar_en_ldap(conexion_ldap, 'uid='+alumno, ['cn','uid','employeeNumber'])
		if len(datos_alumno) == 0:
			alumnos_a_borrar_grupo_students.append(alumno)
		else:
			encontrados += 1
			try:
				if not datos_alumno[0][1].has_key('employeeNumber'):
					print "El alumno",alumno,"no tiene atributo 'employeeNumber'... saltando"
					continue
				if not datos_alumno[0][1].has_key('uid'):
					print "El alumno",alumno,"no tiene atributo 'uid'... saltando"
					continue
				
				nie = datos_alumno[0][1]['employeeNumber'][0]
				print "Actualizando foto del alumno",datos_alumno[0][1]['cn'][0],'Nie:',nie
				archivo_imagen_sin_tipo = '.'+separador+'temp'+separador+nie
				encontrada_foto = False
				for patron in tipos_imagen:
					archivo_imagen = archivo_imagen_sin_tipo+patron
					if existeArchivoImagen(archivo_imagen):
						print "encontrado archivo de imagen",archivo_imagen
						encontrada_foto = True
						break
				if not encontrada_foto:
					alumnos_sin_foto.append(datos_alumno[0][1]['cn'][0]+' Nie:'+nie)
					print "alumno sin archivo de imagen"
					continue
					
				foto = Image.open(archivo_imagen)
				if (not foto.format == 'JPEG'):
					foto.save('foto', 'JPEG')
					foto = Image.open('foto')
				foto.thumbnail((80, 100), Image.ANTIALIAS)
				foto.save('foto', 'JPEG')
				uid = datos_alumno[0][1]['uid'][0]
				dn='uid='+uid+',ou=People,dc=instituto,dc=extremadura,dc=es'
				atributos = [ (ldap.MOD_DELETE,'jpegPhoto',None) ]
				conexion_ldap.modify_s(dn,atributos)
				atributos = [ (ldap.MOD_ADD,'jpegPhoto',open('foto',"rb").read()) ]
				conexion_ldap.modify_s(dn,atributos)
			except ldap.LDAPError,e:
				pass  
				#puede pasar que no exista el nie o bien no tenga atributo, pero 
				#la acción es ignorar los errores...

	os.remove('foto')	
	
	print "Se han procesado",encontrados,"alumn@s..."
	print ""
	print ""
	print "No se han encontrado las fotos de estos",len(alumnos_sin_foto),"alumn@s"
	print "Estos son los alumno@s sin foto:"
	for asinfoto in alumnos_sin_foto:
		print asinfoto
		
	print ""
	print ""
	print "No se han encontrado en la rama People",len(alumnos)-encontrados,"alumn@s"
	if params["depurastudents"] == "on" and len(alumnos) > encontrados:
		print ""
		print ""
		print "Procediendo al borrado de alumnos del grupo students no existentes en la rama People"
		resultado_busqueda_alumnos_modificado = copy.deepcopy(resultado_busqueda_alumnos)
		for aborrar in alumnos_a_borrar_grupo_students:
			print "Borrando membresía de students para el alumno no existente ...",aborrar
			#Eliminamos de la lista de memberUid ese alumno
			resultado_busqueda_alumnos_modificado[0][1]['memberUid'].remove(aborrar)
			#Eliminamos de la lista de member ese alumno
			resultado_busqueda_alumnos_modificado[0][1]['member'].remove('uid='+aborrar+',ou=People,dc=instituto,dc=extremadura,dc=es')
	
		print "Ejecutando modificación en el árbol ldap..."
		dn = 'cn=students,ou=Group,dc=instituto,dc=extremadura,dc=es'
		ldif = modlist.modifyModlist(resultado_busqueda_alumnos[0][1],resultado_busqueda_alumnos_modificado[0][1])
		conexion_ldap.modify_s(dn,ldif)

	print ""
	print ""
	print "Eliminando carpeta temporal..."
	shutil.rmtree("temp")
	print "Desconectando del servidor ldap '"+params["urildap"]+"'"
	desconecta_de_ldap(conexion_ldap)


if __name__ == '__main__':
	main()
