#! /usr/bin/python
# -*- coding: iso-8859-1 -*-

#####################################################
#    Copyright (C) 2006 by Cesar Kallas   	
#    cesarkallas@soulivre.org   
#    http://cesarkallas.soulivre.org
#                                                                          
#    This program 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 2 of the License, or     
#    (at your option) any later version.                                   
#                                                                          
#    This program 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 this program; if not, write to the                         
#    Free Software Foundation, Inc.,                                       
#    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             
#####################################################

import Log, Opcoes, Ambiente, Softwares
import urllib, os, md5

class Acoes(object):
	"""
	Classe que gerencia as acoes (ex. instalacao, atualizacao, desinstalacao
	Essa classe nao executada nada alem de gerenciar as acoes no banco de dados
	"""
	
	def __init__(self):
		# Inicia log
		self.log = Log.Log("Acoes")
		self.log = self.log.get()
				
		# Verifica conexao com banco de dados
		if Ambiente.db == "":
			self.log.error("Acoes: falha na conexao com banco de dados")
			return -1
		elif Ambiente.db == None:
			self.log.error("Acoes: falha na conexao com banco de dados")
			return -1
		else:
			self.db = Ambiente.db
			self.con = Ambiente.con
			
		self.softwares = Softwares.RPM()

	
	def total(self):
		"""
		Retorna o total de acoes cadastradas
		"""
		try:
			self.db.execute("""SELECT COUNT(*) FROM acoes""")
			return self.db.fetchone()
		except Exception:
			self.log.error("Acoes: falha ao contar acoes")
			raise


	def informacao(self, acao_id):
		"""
		Retorna informacao de um determinada acao
		"""
		try:
			self.db.execute("""SELECT * FROM acoes WHERE acao_id='%s'""" % (acao_id))
			return self.db.next()
		except Exception:
			self.log.error("Acoes: falha ao remover acao id:%s", acao_id)
			raise


	def mudaStatus(self, acao_id, status):
		"""
		Muda status de determinada acao
		"""
		try:
			self.db.execute("""UPDATE  acoes SET status_execucao='%s' where acao_id='%s'""" % (status, acao_id))
			self.con.commit()
			return 1
		except Exception:
			self.log.error("Acoes: falha ao atualizar acao id:%s", acao_id)
			raise


	def exporta(self, status=None):
		"""
		Retorna todas acoes de acordo com o status
		"""
		try:
			if status == None:
				self.db.execute("""SELECT * FROM acoes""")
			else:
				self.db.execute("""SELECT * FROM acoes where status_execucao='%s'""" % status)
			return self.db.fetchall()
		except Exception:
			self.log.error("Acoes: falha ao exporta todas acoes")
			raise


	def remove(self, acao_id=None, status=None):
		"""
		Remove acao da base de dados
		"""
		try:
			if status == None:
				if acao_id == None:
					# Todas acoes
					self.db.execute("""DELETE FROM acoes""")
				else:
					# Somente as com id igual ao parametro
					self.db.execute("""DELETE FROM acoes where acao_id='%s'""" % acao_id)
			else:
				if acao_id == None:
					# Somente as com status igual ao parametro
					self.db.execute("""DELETE FROM acoes where status_execucao='%s'""" % status)
				else:
					# As acoes com id e status igual a do parametro
					self.db.execute("""DELETE FROM acoes where status_execucao='%s' and acao_id='%s'""" % (status, acao_id))
			self.con.commit()
			return 1
		except Exception:
			self.log.error("Acoes: falha ao remover acao id:%s", acao_id)
			raise


	def adiciona(self, dicAcao):
		"""
		Adiciona acao na base de dados
		"""
		# Verifica se o parametro eh do tipo dicionario
		if isinstance(dicAcao, dict) != True:
			self.log.error("Acoes: falha ao adicionar nova acao: parametro invalido!")
			return -1
			
		# Campos necessarios para incluir acao
		camposNecessarios = ("acao", "acao_id", "acao_operador",  "acao_data_execucao",
							"software_nome", "software_versao", "software_liberacao",
							"software_tamanho", "software_localizacao",
							"software_md5", "software_plataforma", "status_execucao", "software_nomeArquivo")
		
		# Campos opcionais para incluir acao
		camposOpcionais = ("acao_data_criacao", "software_descricao", "software_grupo")
		
		# Verifica se o dicionario possui os campos necessarios
		for campo in camposNecessarios:
			if dicAcao.get(campo) == None:
				self.log.error("Acoes: falta campo '%s' no parametro passado!", str(campo))
				return -2
				
		# Verifica os campos opcionais
		for campo in camposOpcionais:
			if dicAcao.get(camposOpcionais) == None:
				dicAcao[camposOpcionais] = ""
	
		# Prossegue com a insercao no banco de dados
		try:
			self.db.execute("INSERT INTO acoes \
				(acao, acao_id, acao_operador, acao_data_execucao, acao_data_criacao, \
				software_nome, software_versao, software_liberacao, software_tamanho, \
				software_descricao, software_liberacao, software_localizacao, software_md5, \
				software_plataforma, software_grupo, status_execucao, software_nomeArquivo) \
				values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
				(dicAcao["acao"], dicAcao["acao_id"], dicAcao["acao_operador"], dicAcao["acao_data_execucao"], 
				dicAcao["acao_data_criacao"], dicAcao["software_nome"], dicAcao["software_versao"], 
				dicAcao["software_liberacao"], dicAcao["software_tamanho"], dicAcao["software_descricao"], 
				dicAcao["software_liberacao"], dicAcao["software_localizacao"], dicAcao["software_md5"], 
				dicAcao["software_plataforma"], dicAcao["software_grupo"], dicAcao["status_execucao"], dicAcao["software_nomeArquivo"]))
			self.con.commit()
			return 1
		except Exception:
			raise
			self.log.error("Acao: ao cadastrar nova acao", str(dicAcao))
			return -3

	
	def executa(self, acao_id):
		"""
		Executa determinada acao e muda status dela, caso ela seja executada com sucesso
		"""
		dicAcao = self.informacao(acao_id)
		
		if isinstance(dicAcao, dict) != True:
			self.log.error("Acao: informacao invalida", str(dicAcao))
			return -1
			
		# Verifica se acao foi retornada com sucesso
		if dicAcao == -1:
			self.log.error("Acao: ao executar acao inexistente", str(dicAcao))
			return -1
		
		# Verifica qual tipo de acao
		if dicAcao.get("acao") == "instalar" or dicAcao.get("acao") == "atualizar":
			# Faz o download do software
			down = self.download(Ambiente.servidorWeb + dicAcao["software_localizacao"] + "/" + dicAcao["software_nomeArquivo"], dicAcao["software_nomeArquivo"])
			if down < 1:
				self.log.error("Acao: falha ao fazer download do arquivo: %s", str(dicAcao["software_nomeArquivo"]))
				return -2
			else:
				# Gera assinatura MD5 do arquivo recebido, para comparacao
				arquivoBaixado = open(Ambiente.dirSoftwares +  str(dicAcao["software_nomeArquivo"]), 'rb')
				arquivoBaixadoMD5 = md5.new(arquivoBaixado.read())
				arquivoBaixadoMD5 = arquivoBaixadoMD5 .hexdigest()
				arquivoBaixado.close()
				
				# Compara MD5
				if arquivoBaixadoMD5 != dicAcao["software_md5"]:
					self.log.error("Acao: falha ao verificar md5 do download, Informado: '%s' , Adiquirido:'%s'", str(dicAcao["software_nomeArquivo"]), str(arquivoBaixadoMD5))
				else:
					# Arquivo OK
					# Verifica se o arquivo ja esta instalado
					(nome, versao, release) = self.softwares.estaInstalado(dicAcao["software_nome"])
					
					# Atualizacao
					if versao == dicAcao["software_versao"]:
						# Software ja instalado, faz atualizacao (Desinstala e depois instala)
						status = self.__desinstala(nome, dicAcao)
						if status < 1:
							return status
						return self.__instala(dicAcao)

					# Instalacao
					elif nome == None:
						return self.__instala(dicAcao)
						
		# Desinstalacao
		elif dicAcao.get("acao") == "desinstalar":
			# Arquivo OK
			# Verifica se o arquivo ja esta instalado
			(nome, versao, release) = self.softwares.estaInstalado(dicAcao["software_nome"])
			self.__desinstala(nome, dicAcao)

		else:
			self.log.error("Acao: id:'%s' invalido!", str(acao_id))
			return -100


	def __instala(self, dicAcao):
		# Tenta instalar o software
		resposta = self.softwares.instala(Ambiente.dirSoftwares + dicAcao["software_nomeArquivo"])
		if resposta < 1:
			# Falha ao instalar software
			self.log.error("Acao: falha ao instalar o software: '%s'", str(dicAcao["software_nomeArquivo"]))
			return -10
		else:
			# Muda status da acao para executada
			st = self.mudaStatus(str(dicAcao["acao_id"]), "executada")
			if st < 1:
				self.log.error("Acao: falha ao atualizar status da acao: '%s'", str(dicAcao["acao_id"]))
				return -11
		# Software instalado com sucesso!
		self.log.info("Acao: software '%s' instalado com sucesso!", str(dicAcao["software_nomeArquivo"]))
		Ambiente.rpc.confirmaAcao(dicAcao["acao_id"])
		Ambiente.rpc.softwareInstalado(dicAcao["acao_id"], Ambiente.mac_address, dicAcao["software_md5"])
		return 1


	def __desinstala(self, nome, dicAcao):
		resposta = self.softwares.desinstala(nome)
		if resposta < 1:
			self.log.error("Acao: falha ao remover o software: '%s'", str(dicAcao["software_nomeArquivo"]))	
			return -1
		else:
			Ambiente.rpc.confirmaAcao(dicAcao["acao_id"])
			Ambiente.rpc.softwareRemovido(str(Ambiente.mac_address), str(dicAcao["software_md5"]))
			return 1
			
		
	def download(self, urlArquivo, nomeArquivo):
		"""
		Faz o download do arquivo passado pela urlArquivo
		"""
		# Abre arquivo remoto
		try:
			arquivoRemoto = urllib.urlopen(urlArquivo)
		except:
			raise
			return -1
			
		# Cria arquivo no disco
		try:
			arquivoLocal = open(Ambiente.dirSoftwares + nomeArquivo, 'w')
		except:
			raise
			return -2
			
		# Download do arquivo e grava no disco
		try:
			while 1:
				bytes = arquivoRemoto.read()
				if not bytes:
					break
				arquivoLocal.write(bytes)
			arquivoLocal.close()
			arquivoRemoto.close()
			return 1
		except:
			raise
			return -3
		
	