From 947dc2c6d255227a5eb8da0340eb5e0ddcb6fb02 Mon Sep 17 00:00:00 2001 From: Sebastian Silva Date: Thu, 15 Sep 2011 15:24:52 +0000 Subject: Initial commit to version control. --- diff --git a/LEEME b/LEEME new file mode 100644 index 0000000..a7cac97 --- /dev/null +++ b/LEEME @@ -0,0 +1,11 @@ +Actividad / Programa de recoleccion de datos y seguimiento. +Autor: Sebastian Silva +Miembro de Sugar Labs Global +Copyright (c) 2010 +22.10.2010 +-- +Para ejecutar la actividad abra el archivo xo desde el diario. +El script monitor.py hace lo mismo y ademas instalará el logger de seguimiento y obtendra registro de logs e historial de navegacion. +Esto es para una XO con build 703 (como en despliegue peruano). +Para mayor informacion consultar el instructivo. +En caso de dificultades contactar al autor. diff --git a/RecolectarDatos-13.zip b/RecolectarDatos-13.zip new file mode 100644 index 0000000..db7f0ba --- /dev/null +++ b/RecolectarDatos-13.zip Binary files differ diff --git a/RecolectarDatos.activity/MANIFEST b/RecolectarDatos.activity/MANIFEST new file mode 100644 index 0000000..37bbf8d --- /dev/null +++ b/RecolectarDatos.activity/MANIFEST @@ -0,0 +1,5 @@ +NEWS +activity.py +pippy_app.py +activity/activity-icon.svg +activity/activity.info diff --git a/RecolectarDatos.activity/NEWS b/RecolectarDatos.activity/NEWS new file mode 100644 index 0000000..3b3d18d --- /dev/null +++ b/RecolectarDatos.activity/NEWS @@ -0,0 +1,45 @@ +Version 2 +- Se corrige Bug #2: Se actualiza para que falle gracilmente en caso de no encontrar la version de Azucar (build 703). + +Version 3 - 14.10.2010 +- Ahora guarda todo el diario. +- Pide la fecha al encuestador +- Intenta desmontar la unidad automaticamente. + En su defecto sincroniza la escritura a la memoria. +- Si se encuentra el archivo .xo, se actualiza su fecha para que quede de primer item en el diario. +- Se elimina error en versiones antiguas de Sugar (Keep error: all changes will be lost) + +Version 4 - 15.10.2010 +- Ahora pide la hora actual al encuestador + +Version 5 - 21.10.2010 +- Se incluye un monitor/instalador. +- Ahora revisamos los logs y el diario tambien + +Version 6 - 22.10.2010 +- Ahora revisamos el espacio en disco (maximo para logs 20mb - solo graba si quedan mas de 30mb) + +Version 7 - 22.10.2010 +Bugfix on logger installer + +Version 8 - 22.10.2010 +Extra feedback on success +Extra check for unreadable isolation dirs + +Version 9 - 22.10.2010 +Se limita a 1MB los logs +Se corrige para obtener registros nuevos en el logger +Si encuentra un logger de todas maneras instala encima +Se añade una espera de 30 segundos para que sugar alcance a copiar los logs historicos + +Version 10 - 22.10.2010 +Bugfix se eliminan print de debug dejados por error + +Version 11 - 22.10.2010 - RELEASE +Bugfix user should not wait in interactive mode (30s delay moved) + +Version 12 - 22.10.2010 - Extra para digete +Se ha pedido adicionalmente un dialogo de confirmacion para instalar el logger. + +Version 13 +Urgent bugfix: diff --git a/RecolectarDatos.activity/activity.py b/RecolectarDatos.activity/activity.py new file mode 100755 index 0000000..a881252 --- /dev/null +++ b/RecolectarDatos.activity/activity.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (C) 2007,2008 One Laptop per Child Association, Inc. +# Written by C. Scott Ananian +# +# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +"""Pippy activity helper classes.""" +from sugar.activity import activity + +class ViewSourceActivity(activity.Activity): + """Activity subclass which handles the 'view source' key.""" + def __init__(self, handle, **kwargs): + super(ViewSourceActivity, self).__init__(handle, False) + self.__source_object_id = None # XXX: persist this across invocations? + self.connect('key-press-event', self._key_press_cb) + def _key_press_cb(self, widget, event): + import gtk + if gtk.gdk.keyval_name(event.keyval) == 'XF86Start': + self.view_source() + return True + return False + def view_source(self): + """Implement the 'view source' key by saving pippy_app.py to the + datastore, and then telling the Journal to view it.""" + if self.__source_object_id is None: + from sugar import profile + from sugar.datastore import datastore + from sugar.activity.activity import get_bundle_name, get_bundle_path + from gettext import gettext as _ + import os.path + jobject = datastore.create() + metadata = { + 'title': _('%s Source') % get_bundle_name(), + 'title_set_by_user': '1', + 'suggested_filename': 'pippy_app.py', + 'icon-color': profile.get_color().to_string(), + 'mime_type': 'text/x-python', + } + for k,v in metadata.items(): + jobject.metadata[k] = v # dict.update method is missing =( + jobject.file_path = os.path.join(get_bundle_path(), 'pippy_app.py') + datastore.write(jobject) + self.__source_object_id = jobject.object_id + jobject.destroy() + self.journal_show_object(self.__source_object_id) + def journal_show_object(self, object_id): + """Invoke journal_show_object from sugar.activity.activity if it + exists.""" + try: + from sugar.activity.activity import show_object_in_journal + show_object_in_journal(object_id) + except ImportError: + pass # no love from sugar. + +TARGET_TYPE_TEXT = 80 +class VteActivity(ViewSourceActivity): + """Activity subclass built around the Vte terminal widget.""" + def __init__(self, handle): + import gtk, pango, vte + from sugar.graphics.toolbutton import ToolButton + from gettext import gettext as _ + super(VteActivity, self).__init__(handle) + toolbox = activity.ActivityToolbox(self) + toolbar = toolbox.get_activity_toolbar() + self.set_toolbox(toolbox) + toolbox.show() + + # add 'copy' icon from standard toolbar. + edittoolbar = activity.EditToolbar() + edittoolbar.copy.set_tooltip(_('Copy selected text to clipboard')) + edittoolbar.copy.connect('clicked', self._on_copy_clicked_cb) + edittoolbar.paste.connect('clicked', self._on_paste_clicked_cb) + # as long as nothing is selected, copy needs to be insensitive. + edittoolbar.copy.set_sensitive(False) + toolbox.add_toolbar(_('Edit'), edittoolbar) + edittoolbar.show() + self._copy_button = edittoolbar.copy + + # creates vte widget + self._vte = vte.Terminal() + self._vte.set_size(30,5) + self._vte.set_size_request(200, 300) + font = 'Monospace 10' + self._vte.set_font(pango.FontDescription(font)) + self._vte.set_colors(gtk.gdk.color_parse ('#000000'), + gtk.gdk.color_parse ('#E7E7E7'), + []) + self._vte.connect('selection-changed', self._on_selection_changed_cb) + self._vte.drag_dest_set(gtk.DEST_DEFAULT_ALL, + [ ( "text/plain", 0, TARGET_TYPE_TEXT ) ], + gtk.gdk.ACTION_COPY) + self._vte.connect('drag_data_received', self._on_drop_cb) + # ...and its scrollbar + vtebox = gtk.HBox() + vtebox.pack_start(self._vte) + vtesb = gtk.VScrollbar(self._vte.get_adjustment()) + vtesb.show() + vtebox.pack_start(vtesb, False, False, 0) + self.set_canvas(vtebox) + self.show_all() + # hide the buttons we don't use. + toolbar.share.hide() # this should share bundle. + toolbar.keep.hide() + edittoolbar.undo.hide() + edittoolbar.redo.hide() + edittoolbar.separator.hide() + + # now start subprocess. + self._vte.connect('child-exited', self.on_child_exit) + self._vte.grab_focus() + bundle_path = activity.get_bundle_path() + # the 'sleep 1' works around a bug with the command dying before + # the vte widget manages to snarf the last bits of its output + self._pid = self._vte.fork_command \ + (command='/bin/sh', + argv=['/bin/sh','-c', + 'python %s/pippy_app.py; sleep 1' % bundle_path], + envv=["PYTHONPATH=%s/library" % bundle_path], + directory=bundle_path) + def _on_copy_clicked_cb(self, widget): + if self._vte.get_has_selection(): + self._vte.copy_clipboard() + def _on_paste_clicked_cb(self, widget): + self._vte.paste_clipboard() + def _on_selection_changed_cb(self, widget): + self._copy_button.set_sensitive(self._vte.get_has_selection()) + def _on_drop_cb(self, widget, context, x, y, selection, targetType, time): + if targetType == TARGET_TYPE_TEXT: + self._vte.feed_child(selection.data) + def on_child_exit(self, widget): + """This method is invoked when the user's script exits.""" + pass # override in subclass + + def close(self, skip_save=False): + """Override the close method so we don't try to + create a Journal entry.""" + activity.Activity.close(self, True) + + def save(self): + pass + + +class PyGameActivity(ViewSourceActivity): + """Activity wrapper for a pygame.""" + def __init__(self, handle): + # fork pygame before we initialize the activity. + import os, pygame, sys + pygame.init() + windowid = pygame.display.get_wm_info()['wmwindow'] + self.child_pid = os.fork() + if self.child_pid == 0: + library_path = os.path.join(activity.get_bundle_path(), 'library') + pippy_app_path = os.path.join(activity.get_bundle_path(), 'pippy_app.py') + sys.path[0:0] = [ library_path ] + g = globals() + g['__name__']='__main__' + execfile(pippy_app_path, g, g) # start pygame + sys.exit(0) + super(PyGameActivity, self).__init__(handle) + import gobject, gtk, os + toolbox = activity.ActivityToolbox(self) + toolbar = toolbox.get_activity_toolbar() + self.set_toolbox(toolbox) + toolbox.show() + socket = gtk.Socket() + socket.set_flags(socket.flags() | gtk.CAN_FOCUS) + socket.show() + self.set_canvas(socket) + socket.add_id(windowid) + self.show_all() + socket.grab_focus() + gobject.child_watch_add(self.child_pid, lambda pid, cond: self.close()) + # hide the buttons we don't use. + toolbar.share.hide() # this should share bundle. + toolbar.keep.hide() + +def _main(): + """Launch this activity from the command line.""" + from sugar.activity import activityfactory + from sugar.activity.registry import ActivityInfo + from sugar.bundle.activitybundle import ActivityBundle + import os, os.path + ab = ActivityBundle(os.path.dirname(__file__) or '.') + ai = ActivityInfo(name=ab.get_name(), + icon=None, + bundle_id=ab.get_bundle_id(), + version=ab.get_activity_version(), + path=ab.get_path(), + show_launcher=ab.get_show_launcher(), + command=ab.get_command(), + favorite=True, + installation_time=ab.get_installation_time(), + position_x=0, position_y=0) + env = activityfactory.get_environment(ai) + cmd_args = activityfactory.get_command(ai) + os.execvpe(cmd_args[0], cmd_args, env) + +if __name__=='__main__': _main() diff --git a/RecolectarDatos.activity/activity/activity-icon.svg b/RecolectarDatos.activity/activity/activity-icon.svg new file mode 100644 index 0000000..40f35b4 --- /dev/null +++ b/RecolectarDatos.activity/activity/activity-icon.svg @@ -0,0 +1,32 @@ + + + + + +]> + + + + + + + diff --git a/RecolectarDatos.activity/activity/activity.info b/RecolectarDatos.activity/activity/activity.info new file mode 100644 index 0000000..80bf6b8 --- /dev/null +++ b/RecolectarDatos.activity/activity/activity.info @@ -0,0 +1,11 @@ + +[Activity] +name = Recolectar Datos +bundle_id = org.somosazucar.ActividadRecolectar +service_name = org.somosazucar.ActividadRecolectar +class = activity.VteActivity +icon = activity-icon +activity_version = 13 +mime_types = +show_launcher = yes + diff --git a/RecolectarDatos.activity/pippy_app.py b/RecolectarDatos.activity/pippy_app.py new file mode 100644 index 0000000..950992f --- /dev/null +++ b/RecolectarDatos.activity/pippy_app.py @@ -0,0 +1,540 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2010 - Sebastian Silva +# Equipo de desarrollo de Sugar Labs Global. + +from sugar.datastore import datastore +from sugar import profile +import sys,csv,os,gtk,shutil +from time import localtime, strftime, time, ctime, altzone +from subprocess import Popen,PIPE,STDOUT +try: + from sqlite3 import dbapi2 as sqlite +except: + try: + from pysqlite2 import dbapi2 as sqlite + except: + print "Advertencia: Imposible encontrar bilbiotecas pysqlite." + +version = "13" + +###################################################### +# 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 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: + from gio import VolumeMonitor + vm = VolumeMonitor() + 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: + print "Error leyendo "+path + return None + +class Data_general: + """ + Obtenemos y almacenamos datos para registrar + """ + def __init__(self): + self.retrieved = 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: + self.retrieved = True + # Preparamos el encabezado + self.nick = profile.get_nick_name() + 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 código modular 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 código de alumno", + "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: + from gio import VolumeMonitor + vm = VolumeMonitor() + 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): + try: + if self.events[hora]: + self.events[hora+1] = [ctime(hora), texto ] + except KeyError: + self.events[hora] = [ctime(hora), texto ] + + def collect_logs(self): + """ + guarda en un archivo CSV la info de los logs de de los historiales. + """ + 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) + # creation_time = os.path.getctime(name) + # nota: creation time no es correcto pq los logs han sido movidos a subcarpetas + this_dir = os.path.split(os.path.dirname(name))[1] + if this_dir == "logs": + this_dir = "" + continue + if base == "shellservice.log": + self.event(mtime, "LOG: *** Se inició nueva sesión de Sugar." ) + elif base == "shell.log": + pass + elif base == "presenceservice.log": + pass + elif base == "datastore.log": + pass + else: + self.event(mtime, "LOG: Ultima actividad en log de " + base[:-4]) + + # 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): + print "Imposible encontrar directorio isolation." + return None + 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.sqlite": + shutil.copy (name, "/tmp") + tmpname = os.path.join("/tmp", base) + con = sqlite.connect(tmpname) + cur = con.cursor() + results = cur.execute ("select visit_date, url from moz_historyvisits,moz_places where place_id == moz_places.id") + for r in results: + item_timestamp = float(str(r[0])[:10]) + if r[1][:27] == 'http://localhost:8000/wiki/': + self.event(item_timestamp, "WEB: Articulo Wikipedia: " + r[1][27:]) + elif r[1][:31] == 'http://localhost:8000/search?q=': + self.event(item_timestamp, "WEB: Búsqueda en Wikipedia: " + r[1][31:]) + elif r[1][:29] == 'http://localhost:8000/static/': + self.event(item_timestamp, "WEB: Índice de Wikipedia.") + else: + self.event(item_timestamp, "WEB: Navega a: " + r[1]) + con.close() + os.unlink(tmpname) + + # Vamos a mirar por redes conocidas + nm_file = '/home/olpc/.sugar/default/nm/networks.cfg' + if not os.path.exists(nm_file): + print "Imposible encontrar info de Network Manager." + else: + try: + 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) + except: + print "Advertencia: Imposible leer info de Network Manager." + + + 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: + 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() + wait(30) + dg.update_logger() +elif os.path.split(sys.argv[0])[-1:][0]=='instalar.py': + 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 - Sebastian Silva +Miembro de Equipo Sugar Labs Global. +-- +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 python.\nQue tenga un buen día. :-)') + md.run() + md.destroy() + print 'Ha ejecutado correctamente el programa python.\nQue tenga un buen día. :-)' +else: + # Vamos a recolectar datos pero somos la actividad + print """Actividad Recolectar Datos. +Copyright (c) 2010 - Sebastian Silva +Miembro de Equipo Sugar Labs Global. +-- +Inicio: Cargando los datos del Diario ahora! Porfavor espere... +""" + diario = load_journal_table() + dg = Data_general() + dg.write_csv_diario(diario) + md = gtk.MessageDialog(None, + gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, + gtk.BUTTONS_CLOSE, 'Ha ejecutado correctamente Recolectar Datos.\nNo olvide ejecutar ahora el comando de python.') + md.run() + md.destroy() + print 'Ha ejecutado correctamente Recolectar Datos.\nNo olvide ejecutar ahora el comando de python.' diff --git a/instalar.py b/instalar.py new file mode 100644 index 0000000..7ae1266 --- /dev/null +++ b/instalar.py @@ -0,0 +1,261 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Logger Script +# +# Copyright (c) 2011 - Sebastian Silva +# +# 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 . + +#from sugar.datastore import datastore +#from sugar import profile +import sys,csv,os,shutil +from time import localtime, strftime, time, ctime, altzone, mktime, sleep +from datetime import datetime +from subprocess import Popen,PIPE,STDOUT +try: + from sqlite3 import dbapi2 as sqlite +except: + try: + from pysqlite2 import dbapi2 as sqlite + except: + print "Advertencia: Imposible encontrar bilbiotecas pysqlite." + +####################################################### + +def wait(time_lapse): + sleep(time_lapse) + +#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 _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: + print "Error leyendo "+path + return None + +class Data_general: + """ + Obtenemos y almacenamos datos para registrar + """ + def __init__(self): + self.retrieved = False + self.events = {} + + def event(self, hora, texto): + try: + if self.events[hora]: + self.events[hora+1] = [ctime(hora), texto ] + except KeyError: + self.events[hora] = [ctime(hora), texto ] + + def collect_logs(self): + """ + recolecta la info de los logs de los historiales. + """ + 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) + creation_time = os.path.getctime(name) + # nota: creation time no es correcto pq los logs han sido movidos a subcarpetas + this_dir = os.path.split(os.path.dirname(name))[1] + if this_dir == "logs": #estamos mirando la sesión recien comenzada + this_dir = "" + if base == "shell.log": + pass + elif base == "datastore.log": + pass + elif base == "presenceservice.log": + pass + 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 - probable inicio de sesión" % name ) + elif base == "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." + return None + 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) + + return True + + 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() + wait(30) + dg.update_logger() +elif os.path.split(sys.argv[0])[-1:][0]=='instalar.py': + print "Instalador." + dg = Data_general() + dg.install_logger() + dg.update_logger() + print "Se ha instalado el programa de seguimiento." diff --git a/monitor.py b/monitor.py new file mode 100644 index 0000000..7ae1266 --- /dev/null +++ b/monitor.py @@ -0,0 +1,261 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Logger Script +# +# Copyright (c) 2011 - Sebastian Silva +# +# 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 . + +#from sugar.datastore import datastore +#from sugar import profile +import sys,csv,os,shutil +from time import localtime, strftime, time, ctime, altzone, mktime, sleep +from datetime import datetime +from subprocess import Popen,PIPE,STDOUT +try: + from sqlite3 import dbapi2 as sqlite +except: + try: + from pysqlite2 import dbapi2 as sqlite + except: + print "Advertencia: Imposible encontrar bilbiotecas pysqlite." + +####################################################### + +def wait(time_lapse): + sleep(time_lapse) + +#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 _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: + print "Error leyendo "+path + return None + +class Data_general: + """ + Obtenemos y almacenamos datos para registrar + """ + def __init__(self): + self.retrieved = False + self.events = {} + + def event(self, hora, texto): + try: + if self.events[hora]: + self.events[hora+1] = [ctime(hora), texto ] + except KeyError: + self.events[hora] = [ctime(hora), texto ] + + def collect_logs(self): + """ + recolecta la info de los logs de los historiales. + """ + 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) + creation_time = os.path.getctime(name) + # nota: creation time no es correcto pq los logs han sido movidos a subcarpetas + this_dir = os.path.split(os.path.dirname(name))[1] + if this_dir == "logs": #estamos mirando la sesión recien comenzada + this_dir = "" + if base == "shell.log": + pass + elif base == "datastore.log": + pass + elif base == "presenceservice.log": + pass + 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 - probable inicio de sesión" % name ) + elif base == "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." + return None + 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) + + return True + + 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() + wait(30) + dg.update_logger() +elif os.path.split(sys.argv[0])[-1:][0]=='instalar.py': + print "Instalador." + dg = Data_general() + dg.install_logger() + dg.update_logger() + print "Se ha instalado el programa de seguimiento." -- cgit v0.9.1