#!/usr/bin/python
# -*- coding: utf-8 -*-
# Collector and Logger Script
#
# Copyright (c) 2010-2012 - Sebastian Silva <sebastian@somosazucar.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 3 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, see <http://www.gnu.org/licenses/>.

__version__ = '2012.06.11'
from sugar.datastore import datastore
import sys,csv,os,gtk,shutil
from time import localtime, strftime, time, ctime, altzone, mktime, sleep
from datetime import datetime
from subprocess import Popen,PIPE,STDOUT
from urlparse import urlparse
from urllib import unquote
from collections import defaultdict
try:
    from sqlite3 import dbapi2 as sqlite
except:
    try: 
        from pysqlite2 import dbapi2 as sqlite
    except:
        print "Advertencia: Imposible encontrar bilbiotecas pysqlite."

version = "14"
UPDATE_URL = 'http://people.sugarlabs.org/~icarito/logger/latest_olpc_en_casa'

def get_nick():
    try:
        import gconf
        client = gconf.client_get_default()
        return client.get_string("/desktop/sugar/user/nick")
    except ImportError:
        from sugar import profile
        prof = profile.get_profile()
        return prof.nick_name

######################################################
# Aquí intentamos determinar la versión de Sugar
# el atributo "config.version" contendrá la versión
try:
    # Ubicación estándar (Sugar 0.84+)
    from jarabe import config
    got_config = True
except:
    got_config = False 

if not got_config:
    try:
        # OLPC XO build 802
        sys.path.insert(0, '/usr/share/sugar/shell')
        import config
    except:
        # Builds viejos no tienen este dato (703)
        class dummy_configuration:
            def __init__(self):
                self.version = "n/d"
        config = dummy_configuration()
#######################################################

def updateSelf(filename):
	''' Update the program file with the latest version from the repository '''
    ''' Pulled from youtube-dl '''
	if not os.access(filename, os.W_OK):
		sys.exit('ERROR: no write permissions on %s' % filename)

	print(u'Updating to latest version...')

	try:
		try:
			urlh = urllib.urlopen(UPDATE_URL)
			newcontent = urlh.read()
			
			vmatch = re.search("__version__ = '([^']+)'", newcontent)
			if vmatch is not None and vmatch.group(1) == __version__:
				print (u'logger is up-to-date (' + __version__ + ')')
				return
		finally:
			urlh.close()
	except (IOError, OSError), err:
		sys.exit('ERROR: unable to download latest version')

	try:
		outf = open(filename, 'wb')
		try:
			outf.write(newcontent)
		finally:
			outf.close()
	except (IOError, OSError), err:
		sys.exit('ERROR: unable to overwrite current version')

	print(u'Updated youtube-dl. Restart youtube-dl to use the new version.')

def wait(time_lapse):
    """ Implementa un "sleep timer" compatible con GTK """
    time_start = time()
    time_end = (time_start + time_lapse)
 
    while time_end > time():
        while gtk.events_pending():
            gtk.main_iteration()

def execute_cmd(cmd):
    """ Ejecuta un comando de la terminal y retorna el 
    output del comando.
    """
    p = Popen(cmd, shell=True, bufsize=0,
              stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
    (child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)
    
    return child_stdout_and_stderr.read()

def responseToDialog(entry, dialog, response):
    dialog.response(response)
def getText(markup, markup2=""):
    """ 
    Presenta un diálogo que pregunta algo al usuario.
    Retorna lo que el usuario ingresó.
    """
    dialog = gtk.MessageDialog(
        None,
        gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
        gtk.MESSAGE_QUESTION,
        gtk.BUTTONS_OK,
        None)
    dialog.set_markup(markup)
    entry = gtk.Entry()
    #allow the user to press enter to do ok
    entry.connect("activate", responseToDialog, dialog, gtk.RESPONSE_OK)
    #create a horizontal box to pack the entry and a label
    hbox = gtk.HBox()
    hbox.pack_start(gtk.Label("Name:"), False, 5, 5)
    hbox.pack_end(entry)
    #some secondary text
    dialog.format_secondary_markup(markup2)
    #add it and show it
    dialog.vbox.pack_end(hbox, True, True, 0)
    dialog.show_all()
    #go go go
    dialog.run()
    text = entry.get_text()
    dialog.destroy()
    return text

def load_journal_table():
    """
    Carga la metadata del Diario de Sugar
    Retorna una lista de objetos de datastore
    """
    ds_mounts = datastore.mounts()
    mountpoint_id = None
    if len(ds_mounts) == 1 and ds_mounts[0]['id'] == 1:
        query = { 'sorting':'timestamp' }
    else:
        # we're in sugar 0.82
        query = { 'order_by':'-timestamp' }
        for mountpoint in ds_mounts:
            id = mountpoint['id']
            uri = mountpoint['uri']
            if uri.startswith('/home'):
                mountpoint_id = id

    if mountpoint_id is not None:
        query['mountpoints'] = [ mountpoint_id ]

    ds_objects, num_objects = datastore.find(
        query, properties=['title_set_by_user','activity',
            'title', 'mime_type', 'mtime', 'share-scope','uid',
            'keep', 'tags', 'description'])

    print "Listo! Ingrese los datos que se piden a continuación."
    return ds_objects

def get_media_mountpoint():
    """
    Retorna la URI del primer
    dispositivo USB que encontremos
    """
    ds_mounts = datastore.mounts()
    mountpoint_id = None
    if len(ds_mounts) == 1 and ds_mounts[0]['id'] == 1:
        import gio
        vm = gio.volume_monitor_get()
        mounts = vm.get_mounts()
        for mountpoint in mounts:
            uri = mountpoint.get_root().get_path()
            if uri.startswith('/media'):
                return(uri+"/"), False
        return "", False
    else:
        # we're in sugar 0.82
        for mountpoint in ds_mounts:
            id = mountpoint['id']
            uri = mountpoint['uri']
            if uri.startswith('/media'):
                return(uri+"/"), id
        return "", True 

def _read_file(path):
    """
    Esta función lee y retorna el contenido
    de archivos de texto
    """
    if os.access(path, os.R_OK) == 0:
        return "n/d" 

    fd = open(path, 'r')
    value = fd.read()
    fd.close()
    if value:
        value = value.strip('\n')
        return value
    else:
        return '' 

class Data_general:
    """
    Obtenemos y almacenamos datos para registrar
    """
    def __init__(self):
        self.retrieved_info = False
        self.retrieved_logs = False
        self.events = {}

    def abrir_csv(self, file_suffix = ""):
        """
        Crea un archivo CSV en el USB (si se encuentra)
        retorna el objeto CSV para continuar la escritura.
        Guarda en atributos del objeto los parametros
        para no consultarlos nuevamente.
        """
        if not self.retrieved_info:
            self.retrieved_info = True
            self.nick = get_nick() 
            # Preparamos el encabezado
            self.serial_no = _read_file('/ofw/serial-number')
            self.build_no = str(_read_file('/boot/olpc_build'))
            self.firmware_no = _read_file('/ofw/openprom/model')
            self.sugar_no = config.version
            self.escuela = str(getText("Porfavor indique el código modular de la IE"))
            while len(self.escuela) != 7:
                self.escuela = str(getText("Porfavor indique el <b>código modular</b> de IE",
                                        "El código debe ser de 7 dígitos. Intente nuevamente."))
            self.alumno = str(getText("Porfavor ingrese el código de alumno"))
            while len(self.alumno) != 3:
                self.alumno = str(getText("Porfavor indique el <b>código de alumno</b>",
                                        "El código debe ser de 3 dígitos. Intente nuevamente."))
            self.fecha = strftime("%Y-%m-%d %H:%M:%S", localtime())
            self.fecha_encuestador = str(getText("Porfavor indique la fecha de HOY (en formato DD/MM/AAAA)"))
            self.hora_encuestador= str(getText("Porfavor indique la hora ACTUAL (en formato HH:MM AM/PM)"))
      
        self.media, self.mount_id = get_media_mountpoint()
        if self.media=="":
            self.usb_media = False 
            print "ADVERTENCIA: No se encontró una unidad USB!"
            try:
                self.media=os.environ['SUGAR_ACTIVITY_ROOT']+"/data/"
            except:
                pass
        else:
            self.usb_media = True

        filename = os.path.join(self.media, self.escuela+"_"+self.alumno+file_suffix+".csv")
        if os.path.exists(filename):
            filename = os.path.join(self.media, self.escuela+"_"+self.alumno+file_suffix+"-re.csv")
        fd = open(filename,"wb")
        writer = csv.writer(fd, dialect='excel')

        writer.writerow(['Usuario:', self.nick])
        writer.writerow(['Azucar:', self.sugar_no])
        writer.writerow(['Serie:', self.serial_no])
        writer.writerow(['Ensamble:', self.build_no])
        writer.writerow(['Firmware:', self.firmware_no])
        writer.writerow(['Escuela:', self.escuela])
        writer.writerow(['Alumno:', self.alumno])
        writer.writerow(['Fecha interna:', self.fecha])
        writer.writerow(['Fecha medicion:', self.fecha_encuestador])
        writer.writerow(['Hora medicion:', self.hora_encuestador])
        writer.writerow([])

        return writer, filename, fd

    def write_csv_diario(self, objetos):
        """
        Interroga al usuario
        carga datos de la máquina XO / Sugar
        guarda en un archivo CSV la info del diario.
        """
        writer, filename, fd = self.abrir_csv("-diario")

        # Ordenamos los atributos
        propiedades = {'activity': 'ACTIVIDAD',
                'title_set_by_user': 'TITULADO POR USUARIO',
                'title': 'TITULO',
                'mime_type': 'TIPO',
                'mtime': 'FECHA',
                'share-scope': 'COMPARTIDO',
                'keep': 'FAVORITO',
                'tags': 'ETIQUETAS',
                'description': 'DESCRIPCION'}
        props = propiedades.keys()
        props.sort()
        row=[]
        for p in props:
            row.append(propiedades[p])
        writer.writerow(row)

        for obj in objetos:
            metadata = obj.metadata.get_dictionary()
            row = []
            for p in props:
                try:
                    row.append(metadata[p])
                except:
                    row.append("")
            writer.writerow(row)
            obj.destroy()
            
        fd.close()
        print "Archivo creado en "+filename

    def try_to_unmount(self):
        if self.usb_media:
            activity_bundle_file = self.media+"RecolectarDatos-"+version+".xo"
            if os.path.exists(activity_bundle_file):
                execute_cmd('touch '+activity_bundle_file)
                print "Se actualizó la fecha de " + activity_bundle_file
            execute_cmd('sync')                
            try:
                if self.mount_id!=False:
                    datastore.unmount(self.mount_id)
                    output = execute_cmd('umount ' + self.media[:-1])
                    if len(output)>1:
                        datastore.mount(self.media, "")
                        raise NameError('Imposible desmontar')
                else:
                    import gio
                    vm = gio.volume_monitor_get()
                    mounts = vm.get_mounts()
                    for mountpoint in mounts:
                        uri = mountpoint.get_root().get_path()
                        if uri.startswith('/media'):
                            result = mountpoint.unmount(lambda x,y: None)
                wait(3)
                print "Se ha desmontado automaticamente la memoria USB."
            except:
                print "No olvide desmontar la memoria USB desde el Diario!"

    def event(self, hora, texto):
        while True:
            if self.events.get(hora):
                hora=hora+1
            else:
                self.events[hora] = [ctime(hora), texto ]
                break

    def collect_logs(self):
        """
        guarda en un archivo CSV la info de los logs de de los historiales.
        """
        if not self.retrieved_logs:
            self.retrieved_logs=True
        else:
            return True

        log_dir = os.environ['HOME']+"/.sugar/default/logs"
        if not os.path.exists(log_dir):
            log_dir = "/home/olpc/.sugar/default/logs"        
        if not os.path.exists(log_dir):
            return None
        
        # Navegar recursivamente los directorios de logs
        stack = [log_dir]
        mtime = 0
        while stack:
            directory = stack.pop()
            for base in os.listdir(directory):
                name = os.path.join(directory, base)
                if os.path.isdir(name):
                    if not os.path.islink(name):
                        stack.append(name)
                else:
                    mtime = os.path.getmtime(name)
                    this_dir = os.path.split(os.path.dirname(name))[1]
                    if this_dir == "logs":
                        this_dir = ""
                        if base == "shell.log":
                            pass
                        elif base == "datastore.log":
                            pass
                        elif base == "presenceservice.log":
                            self.event(mtime, "LOG: *** %s - inicio de sesión" % name )
                        else:
                            self.event(mtime, "LOG: Ultima actividad en " + name)
                    else: #estamos mirando una sesión anterior
                        if base == "datastore.log":
                            pass
                        elif base == "presenceservice.log":
                            self.event(mtime, "LOG: *** %s - inicio de sesión" % name )
                        elif base == "shell.log": 
                            if config.version!="n/d": #"sugar 7" no escribe nada en shell.log
                                self.event(mtime, "LOG: Ultima actividad en log de shell, posible fin de sesión: " + name)

                        else:
                            self.event(mtime, "LOG: Ultima actividad en " + name)

        # Ahora vamos a iterar por todos los historiales de Navegador / Wikipedia
        isolation_dir = "/home/olpc/isolation/1/gid_to_data_dir"
        if not os.path.exists(isolation_dir):
            isolation_dir = "/home/olpc/.sugar/default"
        if not os.path.exists(isolation_dir):
            print "Imposible encontrar directorio de los registros del navegador."
        stack = [isolation_dir]
        while stack:
            directory = stack.pop()
            try:
                subdirs = os.listdir(directory)
            except:
                subdirs = []
            for base in subdirs: 
                name = os.path.join(directory, base)
                if os.path.isdir(name):
                    if not os.path.islink(name):
                        stack.append(name)
                else:
                    if base == "places.db":
                        shutil.copy (name, "/tmp")
                        tmpname = os.path.join("/tmp", base)
                        con = sqlite.connect(tmpname)
                        cur = con.cursor()
                        results = cur.execute ("select last_visit, uri, title, visits from places")
                        for r in results:
                            #item_timestamp = float(str(r[0])[:10]) 
                            item_timestamp = mktime(
                                    datetime.strptime(r[0], "%Y-%m-%d %H:%M:%S.%f").timetuple())
                            if r[1][:27] == 'http://localhost:8000/wiki/':
                                self.event(item_timestamp, "WEB: (" + r[1] + ") - Articulo Wikipedia: " + r[1][27:] + " (vez %s)" % r[3])
                            elif r[1][:31] == 'http://localhost:8000/search?q=':
                                self.event(item_timestamp, "WEB: (" + r[1] + ") - Se busca en Wikipedia: " + r[1][31:] + " (vez %s)" % r[3])
                            elif r[1][:29] == 'http://localhost:8000/static/':
                                self.event(item_timestamp, "WEB: (" + r[1] + ") - Inicio de Wikipedia." + " (vez %s)" % r[3])
                            else:
                                self.event(item_timestamp, "WEB: Navega a: (" + r[1] + ") (vez %s)" % r[3])
                        con.close()
                        os.unlink(tmpname)

        # Vamos a mirar por redes conocidas
        nm_file = '/home/olpc/.sugar/default/nm/networks.cfg'
        if os.path.exists(nm_file):
            contents = _read_file(nm_file)
            for line in contents.splitlines():
                if line[:1]=='[':
                    net = line[1:-1]
                elif line[:9]=='timestamp':
                    timestamp = float(line[12:])
                    self.event(timestamp, "RED: Se ha asociado a la red WIFI " + net)
        
        return True

    def write_csv_logs(self):
        if self.collect_logs():
            # Finalmente escupimos todo
            writer, filename, fd = self.abrir_csv("-historial")
            for ev in sorted(self.events):
                writer.writerow(self.events[ev])
            
            fd.close()
            print "Archivo creado en "+filename
            return True
        else:
            print "Ejecutando como actividad."
            return False

    def install_logger(self):
        #copiamos el ejecutable
        logdir =  "/home/olpc/.logger"

        if not os.path.exists(logdir):
            execute_cmd("mkdir -p "+logdir)
        
        logprog = os.path.join(logdir, "logger.py")
        shutil.copy (sys.argv[0], logprog)

        #lo hacemos autoiniciar
        already_installed = False
        xsession = "/home/olpc/.xsession"
        if os.path.exists(xsession):
            contents = _read_file(xsession)
            for line in contents.splitlines():
                if line == "python /home/olpc/.logger/logger.py &":
                    already_installed = True

        if not already_installed:
            execute_cmd("echo 'python /home/olpc/.logger/logger.py &' >> " + xsession)

    def update_logger(self):
        if self.collect_logs():
            logdir =  "/home/olpc/.logger"
            logfile = "historial.csv"

            logpath = os.path.join(logdir, logfile)

            if not os.path.exists(logpath):
                execute_cmd("mkdir -p "+logdir)
                mtime = 0
            else:
                #Obtenemos la fecha de la ultima entrada en el log
                lastlog = execute_cmd("tail -n1 "+logpath)
                lastlog = lastlog.strip('\n')
                try:
                    mtime = float(lastlog[-11:-1])
                except:
                    mtime = 0

                size = os.path.getsize(logpath)
                # poor mans logrotate
                if size>262144: #limit 256k
                    if os.path.exists(logpath+"-3"):
                        execute_cmd("mv "+logpath+"-3 "+logpath+"-4")
                    if os.path.exists(logpath+"-2"):
                        execute_cmd("mv "+logpath+"-2 "+logpath+"-3")
                    if os.path.exists(logpath+"-1"):
                        execute_cmd("mv "+logpath+"-1 "+logpath+"-2")
                    execute_cmd("mv "+logpath+" "+logpath+"-1")

            s = os.statvfs(logdir)
            free_disk_in_mb = (s.f_bsize * s.f_bavail)/(1024*1024)

            if free_disk_in_mb > 30:
                fd = open(logpath,"a")
                writer = csv.writer(fd, dialect='excel')
                for ev in sorted(self.events):
                    if ev > mtime:
                        writer.writerow(self.events[ev])
                        #print self.events[ev]
                writer.writerow([ctime(time()), "LOGGER: Se actualizó registro de seguimiento *** " + str(int(time()))])
                #print ctime() + " updated " + str(int(time()))
                #print ctime(mtime) + " :last log " + str(int(mtime))
                fd.close()
        else:
            print "Imposible cargar logs."
        

# Aquí comienza la ejecución
if os.path.split(sys.argv[0])[-1:][0]=='logger.py':
    # Somos el sistema de seguimiento
    dg = Data_general()
    sleep(30)
    dg.update_logger()
elif os.path.split(sys.argv[0])[-1:][0]=='instalar.py':
    # Somos el instalador
    print "Instalador."
    dg = Data_general()
    dg.install_logger()
    dg.update_logger()
    print "Se ha instalado el programa de seguimiento."
elif os.path.split(sys.argv[0])[-1:][0]=='monitor.py':
    # Somos el instalador-recolector
    print """Progama Recolectar Datos. 
Copyright (c) 2010 - 2012 - Sebastian Silva <sebastian@somosazucar.org>
I+D SomosAzucar.Org - Miembro de la comunidad Sugar Labs
--
Inicio: Cargando los datos de los registros, la navegación y diario ahora! Porfavor espere...
"""
    diario = load_journal_table()
    dg = Data_general()
    dg.write_csv_diario(diario)
    message = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_NONE, u"¿Desea instalar Ud. la aplicacion de seguimiento de uso de la Laptop XO?")
    message.add_button('SI', gtk.RESPONSE_YES)
    message.add_button('No gracias', gtk.RESPONSE_NO)
    resp = message.run()
    if resp == gtk.RESPONSE_YES:
        dg.install_logger()
        dg.update_logger()
    else:
        print "Advertencia: NO se ha instalado el programa de seguimiento!"
    if dg.write_csv_logs():
        dg.try_to_unmount()
    md = gtk.MessageDialog(None, 
        gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, 
            gtk.BUTTONS_CLOSE, 'Ha ejecutado correctamente el programa.\nQue tenga un buen día. :-)')
    md.run()
    md.destroy()
    print 'Ha ejecutado correctamente el programa.\nQue tenga un buen día. :-)'
