From: Leandro Lucarella Date: Thu, 3 Feb 2005 23:57:52 +0000 (+0000) Subject: Se cambia el esquema de la base de datos y se empieza a utilizar PEAR X-Git-Tag: svn_import~110 X-Git-Url: https://git.llucax.com/software/sercom-old.git/commitdiff_plain/2629d689134a52c5d3210e371758931b99692a84?ds=sidebyside Se cambia el esquema de la base de datos y se empieza a utilizar PEAR DB_DataObject. --- 2629d689134a52c5d3210e371758931b99692a84 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..e942bab --- /dev/null +++ b/debian/changelog @@ -0,0 +1,14 @@ +sercom (0.2) unstable; urgency=low + + * Se mejora el log. + * Se agrega un umask para que los archivos creados puedan ser leidos por el + grupo. + + -- Leandro Lucarella Mon, 30 Aug 2004 10:48:56 -0300 + +sercom (0.1) unstable; urgency=low + + * Primera version alpha que solo compila. + + -- Leandro Lucarella Sun, 29 Aug 2004 15:20:25 -0300 + diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..8df4df8 --- /dev/null +++ b/doc/README @@ -0,0 +1,4 @@ +Corrector Automático para Taller de Programación I (75.42) +Universidad de Buenos Aires - Facultad de Ingeniería + + diff --git a/doc/dependencias.txt b/doc/dependencias.txt new file mode 100644 index 0000000..b830f66 --- /dev/null +++ b/doc/dependencias.txt @@ -0,0 +1,3 @@ +PEAR: +===== +DB_DataObject 1.7.2 diff --git a/doc/schema.dia b/doc/schema.dia new file mode 100644 index 0000000..7989f57 Binary files /dev/null and b/doc/schema.dia differ diff --git a/doc/schema.sql b/doc/schema.sql new file mode 100644 index 0000000..556ef70 --- /dev/null +++ b/doc/schema.sql @@ -0,0 +1,132 @@ +-- Esquema de la base de datos + +CREATE TABLE curso +( + id INTEGER PRIMARY KEY, + -- clave + anio INTEGER(4) NOT NULL, + cuatrimestre INTEGER(1) NOT NULL, + curso INTEGER NOT NULL, + -- campos + descripcion VARCHAR(150), + UNIQUE (anio, cuatrimestre, curso) +); + +CREATE TABLE inscripto +( + id INTEGER PRIMARY KEY, + -- clave + padron INTEGER NOT NULL, + curso_id INTEGER NOT NULL, + -- campos + mail VARCHAR(150) NOT NULL, + activo INTEGER(1) NOT NULL DEFAULT 1, + UNIQUE (padron, curso_id) +); + +CREATE TABLE docente +( + id INTEGER PRIMARY KEY, + -- clave + nombre VARCHAR(150) NOT NULL, + -- campos + mail VARCHAR(150) NOT NULL, + corrige INTEGER(1) NOT NULL DEFAULT 1, + UNIQUE (nombre) +); + +CREATE TABLE curso_docente +( + -- clave + curso_id INTEGER NOT NULL, + docente_id INTEGER NOT NULL, + PRIMARY KEY (docente_id, curso_id) +); + +CREATE TABLE ejercicio +( + id INTEGER PRIMARY KEY, + -- clave + nombre VARCHAR(150) NOT NULL, + -- campos + numero INTEGER NOT NULL, + docente_id INTEGER NOT NULL, -- Autor + UNIQUE (nombre) +); + +CREATE TABLE entrega +( + id INTEGER PRIMARY KEY, + -- clave + curso_id INTEGER NOT NULL, + nro_ejercicio INTEGER NOT NULL, + entrega INTEGER NOT NULL, + -- campos + ejercicio_id INTEGER NOT NULL, + desde DATE NOT NULL, + hasta DATE NOT NULL, + UNIQUE (curso_id, nro_ejercicio, entrega) +); + +CREATE TABLE caso_de_prueba +( + id INTEGER PRIMARY KEY, + -- clave + ejercicio_id INTEGER NOT NULL, + nombre VARCHAR(255) NOT NULL, + -- campos + privado INTEGER(1) NOT NULL, + activo INTEGER(1) NOT NULL DEFAULT 1, + parametros VARCHAR(255), + salidas VARCHAR(255), + UNIQUE (ejercicio_id, nombre) +); + +CREATE TABLE intento +( + id INTEGER PRIMARY KEY, + -- clave + inscripto_id INTEGER NOT NULL, + entrega_id INTEGER NOT NULL, + numero INTEGER NOT NULL, + -- campos + llegada DATE NOT NULL, + inicio_pruebas DATE, + fin_pruebas DATE, + compila_ok INTEGER(1), + notificado INTEGER(1) NOT NULL DEFAULT 0, + mail_respuesta VARCHAR(150) NOT NULL, + observaciones VARCHAR(255), + UNIQUE (entrega_id, inscripto_id, llegada) +); + +CREATE TABLE correccion +( + id INTEGER PRIMARY KEY, + -- clave + entrega_id INTEGER NOT NULL, + inscripto_id INTEGER NOT NULL, + -- campos + intento_id INTEGER NOT NULL, -- Último intento exitoso + docente_id INTEGER NOT NULL, -- Corrector + nota INTEGER(2), + observaciones VARCHAR(255), + UNIQUE (inscripto_id, entrega_id) +); + +CREATE TABLE prueba +( + id INTEGER PRIMARY KEY, + -- clave + intento_id INTEGER NOT NULL, + caso_de_prueba_id INTEGER NOT NULL, + -- campos + inicio DATE NOT NULL, + fin DATE, + memoria_ok INTEGER(1), + salida_ok INTEGER(1), + observaciones VARCHAR(255), + UNIQUE (intento_id, caso_de_prueba_id) +); + +-- vim: set et sw=4 ts=4 sts=4 : diff --git a/doc/sercom.ini b/doc/sercom.ini new file mode 100644 index 0000000..09f8e9a --- /dev/null +++ b/doc/sercom.ini @@ -0,0 +1,65 @@ + +; Opciones generales para todos los programadas de sercom +[general] +; Nivel de reporte de errores. +error_reporting = E_COMPILE_ERROR|E_ERROR|E_CORE_ERROR +; Nivel de loggeo: L_NON, L_ERR, L_WRN, L_INF, L_DBG, L_ALL +; (ver T/logconstants.php) +loglevel = L_ALL & ~L_DBG +; Archivo de log (si no se especifica, se usa STDERR) +logfile = /var/log/sercom.log +; Directorio base en donde almacenar los archivos de datos (entregas, intentos) +data_dir = /var/lib/sercom +; Claves para validar la identidad del alumno (a usar para crear el código) +claves = xxx +; Cantidad de segundos a esperar para ver si hay una nueva entrega +intervalo = 5 +; Usuario con el cual ejecutar las pruebas +user = sercom + +[mail] +; E-Mail del administrador, a donde enviar errores graves, avisos, etc +mail_admin = luca@llucax.hn.org +; Dirección desde la cual enviar mails +from = "TALLER DE PROGRAMACION I " +; Descripción del programa de mail (X-Mailer) +mailer = "sercom v0.2" +; Prefijo a poner en el subject +prefijo = "[CORRECTOR]" +; Mailbox local en donde hacer backup de los mails entregados +; (si está vacío no se hace backup) +mbox_bak = /var/lib/sercom/entregas.mbox + +; pop +;server = pop.example.com +;user = user +;pass = pass + +; imap +;server = imap.example.com +;user = test +;pass = test +;protocol = imap +;mailbox = "INBOX" + +; mbox +;protocol = mbox +; path al mbox relativo al home del usuario +;server = taller/sercom/mbox + +; imap - con TSL +server = mail.fi.uba.ar +user = user +pass = xxxx +protocol = imap +options = novalidate-cert/ssl + + +[DB_DataObject] +database = sqlite:////var/lib/sercom/corrector.sqlite +schema_location = T/DBO/schema/ +class_location = T/DBO/ +require_prefix = T/DBO/ +class_prefix = T_DBO_ +;debug = 1 + diff --git a/src/T/DBO/Caso_de_prueba.php b/src/T/DBO/Caso_de_prueba.php new file mode 100644 index 0000000..ccbe0f1 --- /dev/null +++ b/src/T/DBO/Caso_de_prueba.php @@ -0,0 +1,47 @@ + DB_DATAOBJECT_INT, + 'ejercicio_id' => DB_DATAOBJECT_INT, + 'nombre' => DB_DATAOBJECT_STR, + 'privado' => DB_DATAOBJECT_INT + DB_DATAOBJECT_BOOL, + 'activo' => DB_DATAOBJECT_INT + DB_DATAOBJECT_BOOL, + 'parametros' => DB_DATAOBJECT_STR, + 'salidas' => DB_DATAOBJECT_STR, + ); + } + + // now define the keys. + function keys() + { + return array('id'); + } + +} + +?> \ No newline at end of file diff --git a/src/T/DBO/Correccion.php b/src/T/DBO/Correccion.php new file mode 100644 index 0000000..fa13f16 --- /dev/null +++ b/src/T/DBO/Correccion.php @@ -0,0 +1,47 @@ + DB_DATAOBJECT_INT, + 'entrega_id' => DB_DATAOBJECT_INT, + 'inscripto_id' => DB_DATAOBJECT_INT, + 'intento_id' => DB_DATAOBJECT_INT, + 'docente_id' => DB_DATAOBJECT_INT, + 'nota' => DB_DATAOBJECT_INT, + 'observaciones' => DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT, + ); + } + + // now define the keys. + function keys() + { + return array('id'); + } + +} + +?> \ No newline at end of file diff --git a/src/T/DBO/Curso.php b/src/T/DBO/Curso.php new file mode 100644 index 0000000..9bd01a1 --- /dev/null +++ b/src/T/DBO/Curso.php @@ -0,0 +1,70 @@ + DB_DATAOBJECT_INT, + 'anio' => DB_DATAOBJECT_INT, + 'cuatrimestre' => DB_DATAOBJECT_INT, + 'curso' => DB_DATAOBJECT_INT, + 'descripcion' => DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT, + ); + } + + // now define the keys. + function keys() + { + return array('id'); + } + + function toLine() + { + return $this->anio . $this->cuatrimestre . $this->curso; + } + + /** + * + */ + function resultado_de_prueba($prueba, $salida, $descripcion = "") + { + if($memoria)$memoria = 1; + else $memoria = 0; + if($salida)$salida = 1; + else $salida = 0; + $date = date("YmdHis"); + $this->db->query("INSERT INTO prueba + (cuatrimestre, padron, ejercicio, entrega, intento, caso_de_prueba, memoria_ok, salida_ok, descripcion) + VALUES ($this->cuatrimestre, $this->padron, $this->ejercicio, $this->entrega, $this->intento, $prueba->id, + $prueba->memoria, $salida, '$descripcion')"); + $this->db->query("UPDATE intento SET fin_pruebas = '$date' + WHERE cuatrimestre = {$this->cuatrimestre} AND + padron = {$this->padron} AND + ejercicio = {$this->ejercicio} AND + entrega = {$this->entrega} AND + intento = {$this->intento}"); + } + +} + +?> \ No newline at end of file diff --git a/src/T/DBO/Curso_docente.php b/src/T/DBO/Curso_docente.php new file mode 100644 index 0000000..0d623fa --- /dev/null +++ b/src/T/DBO/Curso_docente.php @@ -0,0 +1,37 @@ + DB_DATAOBJECT_INT, + 'docente_id' => DB_DATAOBJECT_INT, + ); + } + + // now define the keys. + function keys() + { + return array('curso_id', 'docente_id'); + } + +} + +?> \ No newline at end of file diff --git a/src/T/DBO/Docente.php b/src/T/DBO/Docente.php new file mode 100644 index 0000000..7215d62 --- /dev/null +++ b/src/T/DBO/Docente.php @@ -0,0 +1,67 @@ + DB_DATAOBJECT_INT, + 'nombre' => DB_DATAOBJECT_STR, + 'mail' => DB_DATAOBJECT_STR, + 'corrige' => DB_DATAOBJECT_INT + DB_DATAOBJECT_BOOL, + ); + } + + // now define the keys. + function keys() + { + return array('id'); + } + + /** + * + * @return array Cursos en los que está el docente. + */ + function getCursos($activo = true) + { + $c = DB_DataObject::factory('curso'); + if ($activo) + { + $c->anio = date('Y'); + $c->cuatrimestre = (date('n') < 8) ? 1 : 2; // Hasta agosto es 1er cuat + } + $cd = DB_DataObject::factory('curso_docente'); + $cd->selectAs(); + $cd->joinAdd($c); + $cd->joinAdd($this); + $cd->find(); + $ret = array(); + while ($cd->fetch()) + { + $c->get($cd->curso_id); + $ret[] = $c; + } + return $ret; + } + +} + +?> \ No newline at end of file diff --git a/src/T/DBO/Ejercicio.php b/src/T/DBO/Ejercicio.php new file mode 100644 index 0000000..d3ca67c --- /dev/null +++ b/src/T/DBO/Ejercicio.php @@ -0,0 +1,46 @@ + DB_DATAOBJECT_INT, + 'nombre' => DB_DATAOBJECT_STR, + 'numero' => DB_DATAOBJECT_INT, + 'docente_id' => DB_DATAOBJECT_INT, + ); + } + + // now define the keys. + function keys() + { + return array('id'); + } + + function getFrom($nombre) + { + $this->nombre = $nombre; + return $this->find(true); + } +} + +?> \ No newline at end of file diff --git a/src/T/DBO/Entrega.php b/src/T/DBO/Entrega.php new file mode 100644 index 0000000..90aa833 --- /dev/null +++ b/src/T/DBO/Entrega.php @@ -0,0 +1,54 @@ + DB_DATAOBJECT_INT, + 'curso_id' => DB_DATAOBJECT_INT, + 'nro_ejercicio' => DB_DATAOBJECT_INT, + 'entrega' => DB_DATAOBJECT_INT, + 'ejercicio_id' => DB_DATAOBJECT_INT, + 'desde' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, + 'hasta' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, + ); + } + + // now define the keys. + function keys() + { + return array('id'); + } + + function getFrom($curso_id, $ejercicio, $entrega) + { + $this->curso_id = $curso_id; + $this->nro_ejercicio = $ejercicio; + $this->entrega = $entrega; + return $this->find(true); + } +} + +?> \ No newline at end of file diff --git a/src/T/DBO/Inscripto.php b/src/T/DBO/Inscripto.php new file mode 100644 index 0000000..0a9d31d --- /dev/null +++ b/src/T/DBO/Inscripto.php @@ -0,0 +1,50 @@ + DB_DATAOBJECT_INT, + 'padron' => DB_DATAOBJECT_INT, + 'curso_id' => DB_DATAOBJECT_INT, + 'mail' => DB_DATAOBJECT_STR, + 'activo' => DB_DATAOBJECT_INT + DB_DATAOBJECT_BOOL, + ); + } + + // now define the keys. + function keys() + { + return array('id'); + } + + function getFrom($padron, $activo = true) + { + $this->padron = $padron; + if (!is_null($activo)) $this->activo = $activo ? 1 : 0; + return $this->find(true); + } + +} + +?> \ No newline at end of file diff --git a/src/T/DBO/Intento.php b/src/T/DBO/Intento.php new file mode 100644 index 0000000..2295955 --- /dev/null +++ b/src/T/DBO/Intento.php @@ -0,0 +1,55 @@ + DB_DATAOBJECT_INT, + 'inscripto_id' => DB_DATAOBJECT_INT, + 'entrega_id' => DB_DATAOBJECT_INT, + 'numero' => DB_DATAOBJECT_INT, + 'llegada' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, + 'inicio_prueba' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, + 'fin_prueba' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, + 'compila_ok' => DB_DATAOBJECT_INT + DB_DATAOBJECT_BOOL, + 'notificado' => DB_DATAOBJECT_INT + DB_DATAOBJECT_BOOL, + 'mail_respuesta' => DB_DATAOBJECT_STR, + 'observaciones' => DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT, + ); + } + + // now define the keys. + function keys() + { + return array('id'); + } + +} + +?> \ No newline at end of file diff --git a/src/T/DBO/Prueba.php b/src/T/DBO/Prueba.php new file mode 100644 index 0000000..53fb1ff --- /dev/null +++ b/src/T/DBO/Prueba.php @@ -0,0 +1,49 @@ + DB_DATAOBJECT_INT, + 'intento_id' => DB_DATAOBJECT_INT, + 'caso_de_prueba_id' => DB_DATAOBJECT_INT, + 'inicio' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, + 'fin' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME, + 'memoria_ok' => DB_DATAOBJECT_INT + DB_DATAOBJECT_BOOL, + 'salida_ok' => DB_DATAOBJECT_INT + DB_DATAOBJECT_BOOL, + 'observaciones' => DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT, + ); + } + + // now define the keys. + function keys() + { + return array('id'); + } + +} + +?> \ No newline at end of file diff --git a/src/T/DBO/schema/taller.links.ini b/src/T/DBO/schema/taller.links.ini new file mode 100644 index 0000000..ec2d860 --- /dev/null +++ b/src/T/DBO/schema/taller.links.ini @@ -0,0 +1,33 @@ + +[inscripto] +curso_id = curso:id + +[curso_docente] +curso_id = curso:id +docente_id = docente:id + +[ejercicio] +docente_id = docente:id + +[entrega] +curso_id = curso:id +ejercicio_id = ejercicio:id + +[caso_de_prueba] +ejercicio_id = ejercicio:id + +[intento] +inscripto_id = inscripto:id +entrega_id = entrega:id + +[prueba] +intento_id = intento:id +caso_de_prueba_id = caso_de_prueba:id + +[correccion] +entrega_id = entrega:id +inscripto_id = inscripto:id +intento_id = intento:id +docente_id = docente:id + +; vim: set et sw=4 sts=4 : diff --git a/src/T/Intento.php b/src/T/Intento.php new file mode 100644 index 0000000..f2716a5 --- /dev/null +++ b/src/T/Intento.php @@ -0,0 +1,290 @@ +__construct($padron, $ej, $ent, $intento, $cuat); + } + + /** + * Constructor (PHP5). + */ + function __construct($padron = null, $ej = null, $ent = null, $intento = null, $cuat = null) + { + $this->padron = $padron; + $this->ejercicio = $ej; + $this->entrega = $ent; + $this->intento = $intento; + $this->cuatrimestre = $cuat; + $this->base_dir = 'intentos'; + } + + /** + * Representación de string + */ + function __toString() + { + return <<padron} + Ejercicio: {$this->ejercicio} + Entrega: {$this->entrega} + Cuatrimestre: {$this->cuatrimestre} + Intento: {$this->intento} +EOT; + } + + /** + * Muestra el intento como una línea. + */ + function to_line() + { + return "{$this->padron} {$this->ejercicio}.{$this->entrega}.{$this->intento} {$this->cuatrimestre}"; + } + + /** + * Toma de los atributos publicos padron, entrega y ejercicio + * En el atributo publico intento deja el numero de intento que corresponderia + * a esta entrega del alumno y en cuatrimestre el cuatrimestre en que cursa + * @param code Código verificador + * @param clave Clave a utilizar para validar el código + * @return string con la descripcion del error, si esta vacio fue ok + */ + function validar_entrega($code, $claves) + { + $query = "SELECT C.id, C.anio, C.cuatrimestre, C.curso + FROM alumno A JOIN cuatrimestre C ON (A.id_cuatrimestre = C.id) + WHERE A.padron = {$this->padron} AND C.activo = 1"; + $inscripto = DB_DataObject::factory('inscripto'); + if (!$inscripto->getFrom($this->padron)) return 'El alumno no está inscripto'; + $inscripto->getLinks(); + $cuat = $inscripto->_curso_id->toLine(); + $this->cuatrimestre = $cuat; + if (!T_chequear_codigo($code, $this->padron, $claves)) + return 'La clave de la entrega no es válida'; + $this->llegada = time(); + $date = date('Y-m-d H:i:s', $this->llegada); + $entrega = DB_DataObject::factory('entrega'); + $entrega->getFrom($inscripto->curso_id, $this->ejercicio, $this->entrega); +var_dump($this); +var_dump($inscripto->id); +var_dump($inscripto->curso_id); +var_dump($entrega->id); +# 2005-01-06, 2005-02-26 20:00:00, 2005-02-03 19:48:15 +var_dump($entrega->desde, $entrega->hasta, $date); +var_dump($entrega->desde <= $date); +var_dump($entrega->hasta >= $date); + if (!($entrega->desde <= $date and $entrega->hasta >= $date)) return 'El ejercicio está siendo entregado fuera de fecha'; + $intento = DB_DataObject::factory('intento'); + $intento->id_entrega = $entrega->id; + $intento->id_inscripto = $inscripto->id; + $cant = $intento->find(); + $this->intento = 1 + $cant; + $this->id_entrega = $entrega->id; + $this->id_inscripto = $inscripto->id; + return ''; + } + + /** + * Toma de los atributos publicos cuatrimestre, padron, entrega, ejercicio, mail e intento + * Debería llamarse antes a validar_entrega siempre + * En el atributo publico intento deja el numero de intento del alumno + * @param mail Mail con el que se recibió la entrega + * @return resultado del query + */ + function hacer_entrega($mail) + { + $intento = DB_DataObject::factory('intento'); + $intento->inscripto_id = $this->id_inscripto; + $intento->entrega_id = $this->id_entrega; + $intento->numero = $this->intento; + $intento->llegada = date('Y-m-d H:i:s', $this->llegada); + $intento->mail_respuesta = $mail; + return $intento->insert(); + } + + /** + * Pone en los atributos publicos cuatrimestre, padron, entrega, ejercicio e intento + * retorna true si encuentra alguno y false sino. + */ + function proximo_a_probar() + { + $result = $this->db->getRow("SELECT cuatrimestre, padron, ejercicio, entrega, intento, mail + FROM intento + WHERE inicio_pruebas is null + ORDER BY llegada + LIMIT 1"); + if (DB::isError($result) or is_null($result)) return false; + $date = date('YmdHis'); + $this->cuatrimestre = $result[0]; + $this->padron = $result[1]; + $this->ejercicio = $result[2]; + $this->entrega = $result[3]; + $this->intento = $result[4]; + $this->db->query("UPDATE intento SET inicio_pruebas = '$date' + WHERE cuatrimestre = {$this->cuatrimestre} AND + padron = {$this->padron} AND + ejercicio = {$this->ejercicio} AND + entrega = {$this->entrega} AND + intento = {$this->intento}"); + return $result[5]; + } + + /** + * Toma de los atributos publicos cuatrimestre, padron, entrega, ejercicio e intento + * el parametro indica si compilo ok es true, sino es false + */ + function informar_compilacion($compilo_ok) + { + if ($compilo_ok) $compilo_ok = 1; + else $compilo_ok = 0; + $date = date('YmdHis'); + + return $this->db->query(" + UPDATE intento SET fin_pruebas = '$date' AND compila_ok = $compilo_ok + WHERE cuatrimestre = {$this->cuatrimestre} AND + padron = {$this->padron} AND + ejercicio = {$this->ejercicio} AND + entrega = {$this->entrega} AND + intento = {$this->intento}"); + } + + /** + * Toma de los atributos publicos cuatrimestre, padron, entrega, ejercicio e intento + * carga el atributo publico caso_de_prueba con el proximo caso de prueba a usar + * devuelve true si encuentra alguno, sino devuelve false + */ + function pedir_caso_de_prueba() + { + $prueba = $this->db->getRow(" + SELECT C.caso_de_prueba, C.privado, C.descripcion, C.parametros, C.salidas + FROM caso_de_prueba as C + WHERE C.cuatrimestre = $this->cuatrimestre AND + C.ejercicio = $this->ejercicio AND + C.entrega = $this->entrega AND + C.caso_de_prueba NOT IN ( + SELECT P.caso_de_prueba from prueba as P + WHERE P.cuatrimestre = $this->cuatrimestre + AND P.entrega = $this->entrega + AND P.ejercicio = $this->ejercicio + AND P.padron = $this->padron + AND P.intento = $this->intento + )"); + if (!is_null($prueba) && !DB::isError($prueba)) { + list($id, $priv, $desc, $params, $salidas) = $prueba; + return new T_Prueba($id, $priv, $desc, $params, $salidas); + } + return $prueba; + } + + /** + * Toma de los atributos publicos cuatrimestre, padron, entrega, ejerciico e intento + * Carga los resultados de una prueba en un nuevo registro en prueba + */ + function resultado_de_prueba($prueba, $salida, $descripcion = "") + { + if($memoria)$memoria = 1; + else $memoria = 0; + if($salida)$salida = 1; + else $salida = 0; + $date = date("YmdHis"); + $this->db->query("INSERT INTO prueba + (cuatrimestre, padron, ejercicio, entrega, intento, caso_de_prueba, memoria_ok, salida_ok, descripcion) + VALUES ($this->cuatrimestre, $this->padron, $this->ejercicio, $this->entrega, $this->intento, $prueba->id, + $prueba->memoria, $salida, '$descripcion')"); + $this->db->query("UPDATE intento SET fin_pruebas = '$date' + WHERE cuatrimestre = {$this->cuatrimestre} AND + padron = {$this->padron} AND + ejercicio = {$this->ejercicio} AND + entrega = {$this->entrega} AND + intento = {$this->intento}"); + } + + /** + * Busca una de los intentos no notificados cual tiene todas las pruebas terminadas y la + * hora de llegada + $hora es menor que ahora. el registro despues se marca como notificado. + * Deja en los atributos publicos cuatrimestre, padron, ejercicio, entrega, intento y mail + * en mail deja la suma del mail del intento y del mail del alumno + * devuelve el/los mails a donde mandar la notificación si encuentra alguno y false sino + */ + function notificacion_pendiente($horas = 1) + { + $date = date("YmdHis"); + $date -= 10000*$horas; + $row = $this->db->getRow("SELECT I.cuatrimestre as cuatrimestre, I.padron, I.ejercicio as ejercicio, + I.entrega as entrega, I.intento, I.mail + FROM intento I JOIN prueba P ON (I.ejercicio = P.ejercicio AND + I.cuatrimestre = P.cuatrimestre AND + I.entrega = P.entrega AND + I.padron = P.padron AND + I.intento = P.intento) + WHERE notificado is null + AND '$date' >= I.llegada + GROUP BY I.cuatrimestre, I.padron, I.ejercicio, I.entrega, I.intento, I.mail + HAVING count(P.memoria_ok) = ( + SELECT count(1) FROM caso_de_prueba C + WHERE ejercicio = C.ejercicio AND + entrega = C.entrega AND + cuatrimestre = C.cuatrimestre)"); + + if(is_null($row) || is_a($row, "db_error")) return false; + $this->cuatrimestre = $row[0]; + $this->padron = $row[1]; + $this->ejercicio = $row[2]; + $this->entrega = $row[3]; + $this->intento = $row[4]; + $mail = $row[5]; + $this->db->query("UPDATE intento SET notificado = 1 + WHERE cuatrimestre = $this->cuatrimestre and + padron = $this->padron and + ejercicio = $this->ejercicio and + entrega = $this->entrega and + intento = $this->intento"); + $mail2 = $this->db->getOne("select mail_alumno FROM alumno + WHERE cuatrimestre = $this->cuatrimestre AND + padron = $this->padron"); + if(strtolower($mail) != strtolower($mail2)) $mail .= ";$mail2"; + + return $mail; + } + + /** + * Obtiene el directorio en el cual se almacena el intento. + */ + function path() { + return $this->base_path() . "/{$this->padron}/{$this->intento}"; + } + + /** + * Obtiene el directorio base de la entrega. + * El directorio base no contempla los datos propios del intento, como + * padrón e intento. + * @param base_dir Directorio base, si no se especifica se usa el directorio + * base del intento. + */ + function base_path($base_dir = null) { + if (is_null($base_dir)) $base_dir = $this->base_dir; + return "$base_dir/{$this->cuatrimestre}/{$this->ejercicio}-{$this->entrega}"; + } + +} + +?> \ No newline at end of file diff --git a/src/T/Prueba.php b/src/T/Prueba.php new file mode 100644 index 0000000..be91862 --- /dev/null +++ b/src/T/Prueba.php @@ -0,0 +1,58 @@ +__construct($id, $priv, $desc, $params, $salidas); + } + + /** + * Constructor (PHP5). + */ + function __construct($id, $priv, $desc, $params, $salidas) + { + $this->id = $id; + $this->privado = $priv ? true : false; + $this->desc = $desc; + $this->params = $params; + $this->salidas = preg_split('/\s*,\s*/', $salidas); + $this->memoria = false; + $this->stdout = 'stdout'; + } + + function to_line() + { + return "id = {$this->id}, priv = {$this->privado}, desc = {$this->desc}, " + . "params = {$this->params}, mem = {$this->memoria}"; + } + + function __toString() + { + return <<id +Privado: $this->privado +Descripción: $this->desc +Parámetros: $this->params +Memoria: $this->memoria + +EOT + . 'Salidas: ' . join(',', $this->salidas); + } + +} + +?> \ No newline at end of file diff --git a/src/T/general.php b/src/T/general.php new file mode 100644 index 0000000..d2abd94 --- /dev/null +++ b/src/T/general.php @@ -0,0 +1,99 @@ + \ No newline at end of file diff --git a/src/sc_fetch b/src/sc_fetch new file mode 100755 index 0000000..2f31525 --- /dev/null +++ b/src/sc_fetch @@ -0,0 +1,281 @@ +#!/usr/bin/php4 +fromaddress . "\n"; + #var_dump(imap_headerinfo($mbox, $i, 80, 80)); +} +*/ + +$claves = $gconf['claves']; + +// Sin cesar. +while (1) { + // Reseteo intervalo + $intervalo = $gconf['intervalo']; + // Abro mailbox o chillo. + if (!($mbox = imap_open($mailbox, @$mconf['user'], @$mconf['pass']))) { + logs('No se pudo conectar al servidor ('.imap_last_error().')', L_ERR); + sleep($intervalo); + } + logs('Conectado como '.@$mconf['user']." a $mailbox", L_DBG); + if (imap_num_msg($mbox) and $hdr = imap_headerinfo($mbox, 1)) { + logs("Nuevo mail '{$hdr->subject}' de {$hdr->fromaddress}"); + @list($padron, $ej, $ent, $codigo) = validar_cabecera($hdr); + if ($padron) { + logs('Cabecera válida', L_DBG); + $intento = new T_Intento($padron, $ej, $ent); + if (!($err = $intento->validar_entrega($codigo, $claves))) { + logs('Entrega aceptada ('.$intento->to_line().')'); + if (!($err = preparar_entrega($intento, $mbox, 1, $gconf['data_dir']))) { + logs('Intento preparado', L_DBG); + $res = $intento->hacer_entrega($hdr->fromaddress); + if (PEAR::isError($res)) { + logs('Error al hacer entrega (' . $res->getMessage() . ')', L_ERR); + enviar_respuesta(R_ERR, $hdr->fromaddress, "Error al preparar entrega:\nError en la base de datos.\n\nSe envió un mensaje al administrador avisando del problema.", $intento); + enviar_respuesta(R_ERR, $mconf['mail_admin'], "Error al preparar entrega:\n" . $res->getMessage(), $intento); + } else { + logs('Intento encolado para compilar'); + enviar_respuesta(R_OK, $hdr->fromaddress, null, $intento); + guardar_mbox($mbox, 1, $hdr, @$mconf['mbox_bak']); + } + } else { + logs("Error al preparar entrega ($err)", L_ERR); + enviar_respuesta(R_ERR, $hdr->fromaddress, "Error al preparar entrega:\n$err", $intento); + enviar_respuesta(R_ERR, $mconf['mail_admin'], "Error al preparar entrega:\n$err.\n\nSe envió un mensaje al administrador avisando del problema.", $intento); + } + } else { + logs("Entrega rechazada ($err)"); + enviar_respuesta(R_ERR, $hdr->fromaddress, $err, $intento); + } + } else { + logs('Entrega rechazada (subject inválido)'); + enviar_respuesta(R_ERR, $hdr->fromaddress, "Asunto (subject) inválido.\n\n" + . "Recuerde que el formato del asunto es estricto:\n" + . "[padrón] [ejercicio].[entrega] [código]\n\n" + . "Donde [padrón] es su número de padrón, [ejercicio] es el número de\n" + . "ejercicio (1 a 4), [entrega] el número de entrega (1 o 2) y [código]\n" + . "es el código verificador que le fue asignado por la cátedra.\n" + ); + } + imap_delete($mbox, 1); + logs('Mail borrado'); + imap_expunge($mbox); + logs('Mensajes purgandos', L_DBG); + $intervalo = 0; // Que tome el próximo mail enseguida. + } else { + logs('No hay mail nuevo', L_DBG); + } + logs('Cerrando conexión', L_DBG); + imap_close($mbox); + sleep($intervalo); +} + + +/* +do { + $mail = imap_fetchstructure($mbox, 1); + var_dump($mail);exit; + for ($mail->parts as $id => $part) { + if ($part->type == TYPEAPPLICATION) { + } + } +} while (!validate_header($header)); + + +file_put_contents('attach', fetchbody_decoded($mbox, 4, 2)) + or die("no se puede escribir archivo de salida\n"); + +imap_close($mbox); +*/ + +function enviar_respuesta($tipo, $to, $mensaje = '', $intento = null) { + global $mconf; + $subject = $mconf['prefijo'] . ' Entrega '; + if ($tipo == R_OK) $estado = 'ACEPTADA'; + else $estado = 'RECHAZADA'; + $subject .= $estado; + $body .= "Estado: $estado\n"; + if ($mensaje) $body .= "\n$mensaje\n"; + if ($intento) $body .= "\n" . $intento->__toString() . "\n"; + logs("Envío de mail '$subject' a '$to'\n$body\n", L_DBG); + $headers = <<subject, $m)) { + return array_slice($m, 1, 5); + } + return false; +} + +function preparar_entrega($intento, $mbox, $msgid, $dir) { + logs('Acá debería verificar el cuerpo del mensaje', L_DBG); + $mail = imap_fetchstructure($mbox, $msgid); + foreach ($mail->parts as $id => $part) { + $fname = part_filename($part); + if ($fname) { + logs("Escribiendo archivo '$fname' [enc={$part->encoding}]", L_DBG); + $body = imap_fetchbody($mbox, $msgid, $id + 1); + $path = "$dir/" . $intento->path(); + if (!mkdir_p($path)) return 'No se pudo crear el directorio'; + if (!file_put_contents("$path/$fname", decode_body($body, $part->encoding))) return "Error al guardar el archivo $fname"; + } + //if (part_is_source($part) $has_sources = true; + //elseif (part_mime_type($part) == 'application/zip') $has_sources = true; + } + return ''; +} + +function part_is_source($part) { + if (part_mime_type($part) == 'application/zip') return true; +/* if (part_mime_type($part) == 'application/octet-stream') { + $extension = (part_filename($part)); + if (, { + } + } */ + return false; +} + +function part_filename($part) { + if (!@$part->dparameters or !is_array($part->dparameters)) { + return ''; + } + foreach ($part->dparameters as $param) { + if ($param->attribute == 'FILENAME') { + return $param->value; + } + } + return ''; +} + +function part_mime_type($part) { + switch ($part->type) { + case TYPETEXT: + return 'text/'.strtolower($part->subtype); + case TYPEMULTIPART: + return 'multipart/'.strtolower($part->subtype); + case TYPEMESSAGE: + return 'message/'.strtolower($part->subtype); + case TYPEAPPLICATION: + return 'application/'.strtolower($part->subtype); + case TYPEAUDIO: + return 'audio/'.strtolower($part->subtype); + case TYPEIMAGE: + return 'image/'.strtolower($part->subtype); + case TYPEVIDEO: + return 'video/'.strtolower($part->subtype); + case TYPEOTHER: + default: + return 'other/'.strtolower($part->subtype); + } +} + +function decode_body($body, $encoding) { + switch ($encoding) { + case ENC7BIT: return fix_eol($body); + case ENC8BIT: return fix_eol($body); //imap_8bit($body); + case ENCBINARY: return imap_binary($body); + case ENCBASE64: return imap_base64($body); + case ENCQUOTEDPRINTABLE: return imap_qprint($body); + case ENCOTHER: return $body; + } + logs('Encoding no reconocido.', L_ERR); + return $body; +} + +function mkdir_p($target) { + if (is_dir($target) or empty($target)) return 1; + if (file_exists($target) and !is_dir($target)) return 0; + if (mkdir_p(substr($target,0,strrpos($target,'/')))) return mkdir($target); + return 0; +} + +function guardar_mbox($mbox, $msgid, $hdr, $mbox_fname) { + if ($mbox_fname) { + $fo = @fopen($mbox_fname, 'a'); + if (!$fo) { + logserr("No se pudo abrir mbox '$mbox_fname'"); + return false; + } + fputs($fo, sprintf("From %s@%s %s\n", $hdr->from[0]->mailbox, $hdr->from[0]->host, date('D M j H:i:s Y'))); + fputs($fo, fix_eol(imap_fetchheader($mbox, $msgid, FT_PREFETCHTEXT))); + fputs("\n"); + fputs($fo, fix_eol(imap_body($mbox, $msgid))); + fclose($fo); + } + return true; +} + +function fix_eol($str) { + return str_replace("\r\n", "\n", $str); +} + +?> \ No newline at end of file diff --git a/src/sc_test b/src/sc_test new file mode 100755 index 0000000..26074ed --- /dev/null +++ b/src/sc_test @@ -0,0 +1,298 @@ +#!/usr/bin/php +proximo_a_probar())) { + logs('No hay intento para probar', L_DBG); + sleep($gconf['intervalo']); + continue; + } + logs('Nuevo intento a probar (' . $intento->to_line() . ')'); + + $intento_dir = "{$gconf['data_dir']}/" . $intento->path(); + $entrega_dir = "{$gconf['data_dir']}/" . $intento->base_path('entregas'); + + if (is_readable("$entrega_dir/Makefile")) { + $makefile = "$entrega_dir/Makefile"; +//XXX @copy("$entrega_dir/Makefile", "$intento_dir/Makefile") +// or logserr("Error al copiar Makefile (de '$entrega_dir/Makefile' a '$intento_dir/Makefile'"); + // Fallback + } elseif (is_readable("{$gconf['data_dir']}/Makefile")) { + $makefile = "{$gconf['data_dir']}/Makefile"; +//XXX if (!@copy("{$gconf['data_dir']}/Makefile", "$intento_dir/Makefile")) { + } else { + enviar_respuesta_error_log($mail, 'No hay un Makefile disponible', $intento); + continue; + } + + $currdir = getcwd(); + if (!@chdir($intento_dir)) { + enviar_respuesta_error_log($mail, 'Error al cambiar al directorio del tp', $intento); + continue; + } + logs("Cambio de directorio '$currdir' -> '$intento_dir'", L_DBG); + logs('Ejecutando el comando: make -f '.escapeshellarg($makefile), L_DBG); + if (exec_get_info('make -f '.escapeshellarg($makefile), $ret, $err, $out)) { + if ($ret) { + logs('Error al compilar'); + logs("Código de retorno: $ret, mensaje: $err)", L_DBG); + //XXX $intento->informar_compilacion(false); + enviar_respuesta(R_ERR, $mail, "ERROR AL COMPILAR!\n\n$err\n\nCódigo de retorno: $ret\n", $intento); + continue; + } else { + logs('Compilado OK'); + //XXX $intento->informar_compilacion(true); + // TODO mail acumulativo + // enviar_respuesta(R_OK, $mail, 'El intento fue compilado con éxito!', $intento); + } + } else { + //XXX $intento->informar_compilacion(false); + enviar_respuesta_error_log($mail, 'No se pudo ejecutar make', $intento); + continue; + } + if (!@mkdir('chroot')) { + enviar_respuesta_error_log($mail, 'Error al crear directorio para chroot', $intento); + continue; + } + if (!@rename('tp', 'chroot/tp')) { + enviar_respuesta_error_log($mail, 'Error al mover el tp al chroot', $intento); + continue; + } + if (!@copy("{$gconf['data_dir']}/redir", 'chroot/redir')) { + enviar_respuesta_error_log($mail, 'Error al copiar redireccionador al chroot', $intento); + continue; + } + if ($err = chmod_own_grp('chroot', 02770, $UID, $GID)) { + enviar_respuesta_error_log($mail, $err, $intento); + continue; + } + if ($err = chmod_own_grp('chroot/tp', 0550, $UID, $GID)) { + enviar_respuesta_error_log($mail, $err, $intento); + continue; + } + if ($err = chmod_own_grp('chroot/redir', 0550, 0, 0)) { + enviar_respuesta_error_log($mail, $err, $intento); + continue; + } + + // comienza la ejecución de casos de prueba + while ($prueba = $intento->pedir_caso_de_prueba()) { + logs('Prueba: ' . $prueba->to_line(), L_DBG); + // ejecuta con fork + $pid = pcntl_fork(); + if ($pid == -1) { + // Error al forkear + $intento->resultado_de_prueba($prueba, false, 'Error al forkear proceso'); + enviar_respuesta_error_log($mail, 'Error al forkear proceso', $intento); + continue 2; + } elseif ($pid) { + // Estamos en el padre, controlamos el tiempo. + logs("En el padre (hijo: $pid)", L_DBG); + // TODO controlar tiempo. + pcntl_waitpid($pid, $exitcode); + logs("Fin de ejecución de caso de prueba (hijo: $pid, ret: $exitcode)", L_DBG); + $stderr = false; // FIXME ver si salida de error es vacia. + if ($exitcode or $stderr) { + logs('Comando ejecutado ERROR', L_DBG); + if ($ret) { + $msg = "El programa salió con código de error $ret"; + $msgs[] = $msg; + logs($msg, L_DBG); + } + if ($stderr) { + $msg = "El programa imprimió mensajes en la salida de error: '$stderr'"; + $msgs[] = $msg; + logs($msg, L_DBG); + } + $msg = join("\n", $msgs); + $intento->resultado_de_prueba($prueba, false, $msg); + enviar_respuesta(R_ERR, $mail, $msg, $intento); + exit(0); // salgo ok del hijo + } else { // Sin errores en la ejecución. + logs('Comando ejecutado OK', L_DBG); + // TODO + $salidas = array(); + foreach ($prueba->salidas as $salida) { + // TODO hacer diffs. + if ($salida == 'stdout') { + $salidas[$salida] = $stdout; + } else { + $salidas[$salida] = file_get_contents($salida); + } + } + logs('Salidas: ' . var_export($salidas, true), L_DBG); + $intento->resultado_de_prueba($prueba, true, 'Salidas: ' . var_export($salidas, true)); + enviar_respuesta(R_ERR, $mail, 'Salidas: ' . var_export($salidas, true), $intento); + exit(0); // salgo ok del hijo + } + } else { + // Estamos en el hijo, corremos en chroot. + logs('En el hijo', L_DBG); + // Hago chroot. + if (!@chroot('chroot')) { + $intento->resultado_de_prueba($prueba, false, 'Error al hacer chroot'); + enviar_respuesta_error_log($mail, 'Error al hacer chroot', $intento); + exit(1); // salgo del hijo + } + logs('Chrooteado, cwd = '.getcwd(), L_DBG); + if (!@posix_setuid($usrinfo['uid'])) { + $intento->resultado_de_prueba($prueba, false, "Error al cambiar al uid '{$usrinfo['uid']}'"); + enviar_respuesta_error_log($mail, "Error al cambiar al uid '{$usrinfo['uid']}'", $intento); + exit(2); // salgo del hijo + } + // TODO poner stdout y stderr en archivos para hacer diff o hacer diffs en memoria + $stdout = null; + if (in_array('stdout', $prueba->salidas)) $stdout = ''; + logs('Se ejecutará: chroot chroot /tp ' . escapeshellarg($prueba->params), L_DBG); + if (!pcntl_exec('/redir', array('tp', 'stdin', 'stdout', 'stderr', '/tp', '0', $prueba->params))) { + logserr('No se pudo ejecutar el comando'); + $intento->resultado_de_prueba($prueba, false, 'No se pudo ejecutar el tp'); + enviar_respuesta_error_log($mail, 'No se pudo ejecutar el tp', $intento); + exit(4); // salgo del hijo + } + exit(100); // salgo del hijo (no deberia llegar nunca aca). + } + break; // FIXME + } + + // TODO make clean +} + + +/** + * Ejecuta un comando devolviendo el código de error y las salidas. + * + * @param cmd Comando a ejecutar. + * @param ret Código de retorno. + * @param err Salida de error (se obtiene sólo si es != null). + * @param out Salida estándar (se obtiene sólo si es != null). + * @param in Entrada a enviarle al comando (null si no se le envía nada). + * @return true si se ejecutó el comando, false si no. + */ +function exec_fds($cmd, &$ret, $in = false, $out = false, $in = false) { + $descriptors = array(); + if ($in) $descriptors[0] = get_fd_type($in, 'r'); + if ($out) $descriptors[1] = get_fd_type($out, 'w'); + if ($err) $descriptors[2] = get_fd_type($err, 'w'); +var_dump($descriptors); + $proc = proc_open($cmd, $descriptors, $pipes); + if (is_resource($proc)) { + $ret = proc_close($proc); + return true; + } + return false; +} + +/** + * Obtiene un tipo de descriptor para proc_open(). + * + * @param fd Tipo de descriptor: si es true, se usa un pipe, si es un array + * se usa el array y si es otra cosa se usa un archivo con este + * nombre. + * @param mode Modo de apertura, sólo se usa si es true o string. + * @return Array a pasar a proc_open(). + */ +function get_fd_type($fd, $mode = false) { + if ($fd === true) { + return array('pipe', $mode); + } elseif (is_array($fd)) { + return $fd; + } + return array('file', $fd, $mode); +} + +/** + * Ejecuta un comando devolviendo el código de error y las salidas. + * + * @param cmd Comando a ejecutar. + * @param ret Código de retorno. + * @param err Salida de error (se obtiene sólo si es != null). + * @param out Salida estándar (se obtiene sólo si es != null). + * @param in Entrada a enviarle al comando (null si no se le envía nada). + * @return true si se ejecutó el comando, false si no. + */ +function exec_get_info($cmd, &$ret, &$err, &$out, $in = null) { + $descriptors = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w')); + $proc = proc_open($cmd, $descriptors, $pipes); + if (is_resource($proc)) { + if (is_null($in)) { + fputs($pipes[0], $in); + } + if (is_null($out)) { + $out = stream_get_contents($pipes[1]); + } + if (is_null($err)) { + $err = stream_get_contents($pipes[2]); + } + foreach (array(0,1,2) as $i) { + fclose($pipes[$i]); + } + $ret = proc_close($proc); + return true; + } + return false; +} + +function enviar_respuesta($tipo, $to, $mensaje = '', $intento = null) { + $mconf = $GLOBALS['CONF']['mail']; + $subject = $mconf['prefijo'] . ' Prueba '; + if ($tipo == R_OK) $estado = 'OK'; + else $estado = 'ERROR'; + $subject .= $estado; + $body .= "Estado: $estado\n"; + if ($mensaje) $body .= "\n$mensaje\n"; + if ($intento) $body .= "\n" . $intento->__toString() . "\n"; + logs("Envío de mail '$subject' a '$to'\n$body\n", L_DBG); + $headers = << \ No newline at end of file