Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Silva <sebastian@sugarlabs.org>2011-09-15 15:24:52 (GMT)
committer Sebastian Silva <sebastian@sugarlabs.org>2011-09-15 15:24:52 (GMT)
commit947dc2c6d255227a5eb8da0340eb5e0ddcb6fb02 (patch)
treea8875a0993a963a2fd7e675d66bc05bb461f22fe
Initial commit to version control.HEADmaster
-rw-r--r--LEEME11
-rw-r--r--RecolectarDatos-13.zipbin0 -> 12155 bytes
-rw-r--r--RecolectarDatos.activity/MANIFEST5
-rw-r--r--RecolectarDatos.activity/NEWS45
-rwxr-xr-xRecolectarDatos.activity/activity.py210
-rw-r--r--RecolectarDatos.activity/activity/activity-icon.svg32
-rw-r--r--RecolectarDatos.activity/activity/activity.info11
-rw-r--r--RecolectarDatos.activity/pippy_app.py540
-rw-r--r--instalar.py261
-rw-r--r--monitor.py261
10 files changed, 1376 insertions, 0 deletions
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 <sebastian@sugarlabs.org>
+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 <cscott@laptop.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., 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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">
+ <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#FFFFFF">
+]><!--"-->
+<svg version="1.1" id="Pippy_activity" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="47.585" height="49.326"
+ viewBox="0 0 47.585 49.326" overflow="visible" enable-background="new 0 0 47.585 49.326" xml:space="preserve">
+
+<path
+ fill="&fill_color;" stroke="&stroke_color;" stroke-width="2" d="M 30.689595,16.460324 L 24.320145,12.001708 L 2.7550028,23.830689 L 23.319231,38.662412 L 45.157349,26.742438 L 36.877062,21.100925" id="path3195" />
+<path
+ fill="&fill_color;" stroke="&stroke_color;" stroke-width="2"
+ nodetypes="cscscssscsssssccc"
+ d="M 12.201296,21.930888 C 13.063838,20.435352 17.035411,18.617621 20.372026,18.965837 C 22.109464,19.147161 24.231003,20.786115 24.317406,21.584638 C 24.401593,22.43057 25.386617,24.647417 26.88611,24.600494 C 28.114098,24.562065 28.61488,23.562481 28.992123,22.444401 C 28.992123,22.444401 28.564434,17.493894 31.897757,15.363536 C 32.836646,14.763482 35.806711,14.411448 37.249047,15.221493 C 38.691382,16.031536 37.648261,19.495598 36.785717,20.991133 C 35.923174,22.48667 32.967872,24.980813 32.967872,24.980813 C 31.242783,27.971884 29.235995,28.5001 26.338769,28.187547 C 23.859153,27.920046 22.434219,26.128159 21.837191,24.708088 C 21.323835,23.487033 20.047743,22.524906 18.388178,22.52176 C 17.218719,22.519542 14.854476,23.017137 16.212763,25.620664 C 16.687174,26.53 18.919175,28.917592 21.08204,29.521929 C 22.919903,30.035455 26.713699,31.223552 30.30027,31.418089 C 26.770532,33.262079 21.760623,32.530604 18.909599,31.658168 C 17.361253,30.887002 9.0350995,26.651992 12.201296,21.930888 z "
+ id="path2209" />
+<path
+ fill="&fill_color;" stroke="&stroke_color;" stroke-width="1"
+ d="M 37.832194,18.895786 C 36.495131,19.851587 34.017797,22.097672 32.3528,21.069911"
+ id="path2211"
+ transform-center-y="-3.6171625"
+ transform-center-x="-0.50601649" />
+<circle
+ fill="&stroke_color;" stroke="none" stroke-width="0"
+ cx="33.926998"
+ cy="6.073"
+ r="1.927"
+ id="circle2213"
+ transform="matrix(0.269108,-0.4665976,-0.472839,-0.2655557,26.503175,35.608682)"
+ />
+</svg>
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 <sebastian@somosazucar.org>
+# 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 <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:
+ 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 <sebastian@somosazucar.org>
+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 <sebastian@somosazucar.org>
+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 <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/>.
+
+#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 <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/>.
+
+#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."