martes, 30 de diciembre de 2014

Gestión de usuarios Open Source en Lazarus

Es un proyecto en guithub.com, y os lo podeis descargar con alguna aplicación git para clonarlo desde

o podeis visitarlo y bajarlo (boton DOWNLOAD ZIP) desde aquí

Estas son algunas de sus pantallas:










De momento y para comprobar su uso tiene dos pequeños mantenimientos generales para comprobar las funciones del usuario.

Los registros nunca se borran, sino que se marcan como borrados. De tal manera que en cualquier momento se pueden  consultar o volver a dar de alta. Esto es una ventaja para las tablas que tengan foreignkey.

Hablando de tablas, de momento sólo funciona con MySQL. Dentro del proyecto hay una carpeta SQL con la última versión de las tablas y algunos registros de ejemplo.

En cualquier momento, como el usuario se identifica con una contraseña, cualquier registro cuando se crea, se modifica, se borra ... la aplicación lleva un registro de los continuos cambios que se realizan sobre él. En cualquiera de los mantenimientos debajo de los Grid y al lado del navigator, tenéis un botón de color azul que llamará al histórico del registro. En la pantalla que aparece vemos los últimos cambios realizados al registro (parte de la izquierda) y en la derecha tenemos el histórico del mismo.

En la carpeta INI del proyecto, configuramos los datos de conexión con la BD y hay un parámetro que nos permite decirle a la aplicación cuanto tiempo debe de esperar para preguntar otra vez por la contraseña en caso de no usar la aplicación.

Esto es útil por si el usuario se marcha del ordenador dejando abierta la aplicación. Si el usuario tiene todos los permisos, cuando la vuelva a preguntar por la contraseña nos dará tres intentos (siempre son tres intentos). Si no introduce la contraseña correcta, se saldrá de la aplicación. Si el usuario ya se identificó, volverá a preguntar por el mismo usuario (su contraseña).

Si el usuario no tiene todos los privilegios, al cabo del tiempo estipulado en el fichero INI, la aplicación se cerrará. 

Un usuario que no tiene permisos de super usuario (todos), hay que darle permisos para cada módulo. Un módulo puede tener permisos para INSERTAR, MODIFICAR, IMPRIMIR u OTROS(normalmente sólo para consulta). Si un usuario entra en un módulo que sólo tenga permisos de INSERTAR, lógicamente sólo permitirá esta opción.

Cuando el usuario se identifica por primera vez, su menú se adaptará a sus permisos, es decir, sólo se visualizarán aquellas opciones que tenga permisos.

Cada mantenimiento tiene un sistema de filtros, configurables por el usuario. Es decir, que el usuario podrá filtrar por los campos que quiera y usando en la SELECT oportuna la combinación de OR y AND que vea oportuna.

Si uno de esos campos por los que quiere filtrar, es un campo foreign key con otra tabla aparecerá a su derecha un botón para enlazar con el mantenimiento de la otra tabla ligada y así poder elegir el registro en concreto que quiere para esa id (foreign key) a filtrar.

Ahora estoy trabajando en la posibilidad de crear un fichero .log en el que se guarden todos los errores de la aplicación.

Bueno, que más deciros no sabría ahora mismo. Pero si que os pediría que si le veis algún fallo o alguna necesidad al mismo proyecto, pues que lo publiquéis en este mismo blog.


lunes, 3 de noviembre de 2014

Parte 1ª - Tratamiento de cadenas de texto en Lazarus

En Lazarus tenemos un amplio repertorio de funciones para el análisis y manipulación de cadenas de texto. Así que sin más tardar pasemos a detallar algunas de las más importantes


AnsiCompareStr(const S1, const S2) : Integer;

Compara dos cadenas haciendo caso omiso de acentos. Esta comparación devuelve el siguiente resultado:

La comparación usa dos cadenas de texto (caracter a caracter) y nos dice si son iguales. Tiene en cuenta los caracteres ANSI(caracteres acentuados) y efectúa diferencia entre mayúsculas y minúsculas.

0 si S1 es igual que S2. Si ambas cadenas son iguales devuelve un 0.
> 0 si S1 es mayor que S2Devolverá 1 si S1 es superior a S2 
< 0 si S1 es menor que S2. Devuelve un -1 si S1 es inferior a S2

Veamos unas comparaciones:

AnsiCompareStr( 'HOLA', 'HOLA' ); {devuelve 0}
AnsiCompareStr( 'HOLA', 'HOLa' ); {devuelve  1}
AnsiCompareStr( 'HOLa', 'HOLA' ); {devuelve -1}

¿Cuando considera Lazarus que una cadena de texto es superior a otra?, pues el orden es el siguiente:

Letras mayúsculas > Letras minúsculas
Letras minúsculas > Números

Veamos un ejemplo(copiado de la wiki de lazarus) :

Program Comparamos;

{$H+}

Uses sysutils;

Procedure Comprobar(S1, S2 : String);
Var R : Longint;
begin
  R := AnsiCompareStr(S1,S2);
  Write ('"',S1,'" is ');
  If R < 0 then
    write ('less than ')
  else 
    begin
      If R = 0 then
           Write ('equal to ')
      else Write ('larger than ');
    end;
  Writeln ('"',S2,'"');
end;

Begin
  Comprobar('One string','One smaller string');
  Comprobar('One string','one string');
  Comprobar('One string','One string');
  Comprobar('One string','One tall string');
End.


AnsiCompareText(const S1, const S2) : Integer;
Es similar a AnsiCompareStr, pero se diferencia en que no distingue entre mayúsculas y minúsculas.


Veamos unas comparaciones:
AnsiCompareText( 'HOLA', 'HOLA' );  {devuelve 0}
AnsiCompareText( 'HOLA', 'HOLa' );  {devuelve 0}
AnsiCompareText( 'HOLa', 'HOLA' );  {devuelve 0}
AnsiCompareText( 'HOLA', 'HOLLA' ); {devuelve -1}
AnsiCompareText( 'HOLLA', 'HOLA' ); {devuelve 1}

El orden ahora entre cadenas es el siguiente:

Letras > Números

AdjustLineBreaks(const S);

Cambiará todas las apariciones de los caracteres #13 y #10 por los caracteres de fin de línea correctos para el sistema operativo en el que se ejecute. Para windows sería #13 y #10. Para linux y Unix sería #10 y para Mac OS es #13.

viernes, 31 de octubre de 2014

Parte 2ª - Excepciones, tratamiento de errores en Lazarus

En la última publicación acordemos que veríamos una serie de situaciones comunes donde es necesario un control de errores y cómo se corrigen los mismos. Así que ahí es donde entraremos en materia a continuación ...


División por cero

Cada vez que hacemos una división estamos propensos al error que se produce si el divisor es cero. Según se trate de una división entera (div) o de una de punto flotante, se dispara una excepción eDivByZero o eZeroDivide, respectivamente.

Las siguientes funciones (líneas de abajo) permiten probar un método de recuperación para este tipo de error. En este caso no se presenta ningún mensaje al usuario, sólo se devuelve un valor por defecto.

function DivisionEntera (a,b: integer): integer;
begin
  try
    Result:= a div b;
  except
    on eDivByZero do
      Result:= 0;
  end;
end;

function DivisionReal (a,b:real): real;
begin
  try
    Result:= a/b;
  except
    on eZeroDivide do
      Result:= 0.0;
  end;
end;
Acceso a un archivo

Abrir un archivo y leer datos.
El siguiente listado abre un archivo y lee 50 bytes en un array, mostrando un mensaje si se produce cualquier error en el proceso(líneas después de Except).

var
  f: file;
  b: array[0..49 of byte];
begin
  try
    AssignFile(f,’nombre’);
    ResetFile(f,1);
    BlockRead(f,b,50);
    CloseFile(f);
  except
    on eInOutError do
      ShowMessage(‘Error al trabajar con el archivo’);
  end;
end;

Abrir un archivo. Bloques anidados
El listado siguiente se puede usar cuando necesitamos utilizar un archivo. Si existe, se abre y si no se crea. Tenemos dos bloques protegidos anidados, uno dentro del otro. El interior captura un error al abrir (por ejemplo, cuando no existe el archivo), mientras que el exterior intercepta un posible error de creación, presentando un mensaje al usuario y abandonando inmediatamente el procedimiento. 

Si todo va bien, podemos utilizar el archivo abierto (con posible comprobación de errores al leer o escribir) y finalmente se cierra el archivo.

Var
  f: file;
begin
  try
    try
      AssignFile(f,’ARCH.TXT’);
      Reset(f,1);
    except
      On eInOutError do
        Rewrite(f,1);
    end;
  except
    on eInOutError do 
    begin
        ShowMessage(‘No se puede crear el archivo’);
        Exit;
    end;
  end;

  {cerramos el archivo}
  CloseFile(f);
end;

Pero tenemos que tener claro que si se produce un error al abrir o leer el archivo, la instrucción de cierre no se ejecutará. En el caso de una lectura no hay mayores problemas, pero cuando modificamos el contenido del archivo es indispensable cerrarlo correctamente. Veremos más sobre esto al tratar el bloque de protección try..finally.


La estructura try ... finally

Como vimos en el ejemplo anterior de acceso a archivos, hay veces que es necesario ejecutar una porción de código (suceda un error o no). Para ello tenemos la estructura try..finally. Cuando se produce un error dentro de este bloque, se suspende el tratamiento de la excepción para ejecutar el código que sigue a finally. Luego de terminado, se sigue con el proceso normal de proceso del error.

La estructura es una variación de los bloques de protección que vimos antes, donde en lugar de except utilizamos la palabra reservada finally.

try
  {código expuesto a errores}
finally
  {código de ejecución obligatoria}
end;

En el ejemplo siguiente controlamos la excepción de un error mediante try..finally. Su finalidad es la siguiente, cuando realizamos una tarea que puede extenderse en el tiempo, es bueno indicarlo al usuario mediante el cursor de espera (el reloj de arena ... crHourglass). Al terminar la operación debemos restituir el cursor anterior. Ahora bien, si ocurre un error durante el proceso, la instrucción de restitución del cursor no se ejecutará nunca y el usuario puede quedarse horas esperando que el cursor le indique que puede seguir trabajando. 

Podemos evitar esta situación con el código del listado siguiente:

try
  screen.cursor:= crHourglass; {ponemos el cursor de espera}
  {aquí se hace el proceso}
finally
  screen.cursor:= crDefault; {restituimos el cursor por defecto}
end;

Pero hay que tener algo muy claro, si en la sección finally se produce un error, la ejecución saltará inmediatamente a la siguiente capa de protección (sección except correspondiente o al manejador por defecto de la aplicación). Por lo tanto, debemos evitar en esta sección utilizar código propenso a errores, o desactivar la detección de los mismos.

Los bloques try..except y try..finally pueden anidarse. Podemos ver un ejemplo de esta técnica
en el listado de más abajo. Este código nos permite ver en una ventana el texto de un archivo junto a los códigos hexadecimales que corresponden a cada carácter. La imagen del form principal, también os la pongo más abajo, despues del listado.

unit Excep_1;

interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, Buttons;
const
  Linea = 10;
type
  tArra = array[0..linea*10-1] of byte;
  TForm1 = class(TForm)
    Edit1: TEdit;
    Label1: TLabel;
    Memo1: TMemo;
    Button1: TButton;
    SpeedButton1: TSpeedButton;
    OpenDialog1: TOpenDialog;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    procedure MuestraHexa(b:tArra; Cant:word);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
implementation

{$R *.DFM}

procedure tForm1.MuestraHexa(b:tArra; Cant:word);
var
  i,j:word;
  s,s2:string;
  bt: byte;
begin
  i:= 0;
  while i*linea<Cant do 
  begin
    s:= '';
    s2:= '';
    for j:= 0 to linea-1 do 
    begin
      if i*linea+j>cant then break;

      bt:= b[i*linea+j];
      s:= s+IntToHex(bt,2)+' ';

      if bt>31 then
           s2:= s2+Chr(bt)
      else s2:= s2+' ';
    end;

    try
      Memo1.lines.add(s+s2);
    except
      on Exception do 
      begin
        ShowMessage('No se pueden ingresar más líneas');
        abort;
      end;
    end;

    inc(i);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
Var
  f: file;
  buf: tArra;
  leidos:integer;
begin
  try
    AssignFile(f,Edit1.text);
    Reset(f,1);
  except
    On eInOutError do 
    begin
      ShowMessage('No se puede abrir el archivo');
      exit;
    end;
  end;

  try
    try
      while not eof(f) do 
      begin
        BlockRead(f,buf,SizeOf(buf),Leidos);
        MuestraHexa(buf,leidos);
      end;
    except
      on eInOutError do
        ShowMessage('Error al leer el archivo');
    end;
  finally
    CloseFile(f);
  end;
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    edit1.text:= OpenDialog1.Filename;
  end;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  Memo1.height:= ClientHeight-64;
end;

end.


Uso de finally, ejemplos comunes

Repasaremos a continuación los casos más comunes que necesitan código de ejecución obligatoria.

Cerrar un archivo.
Retomemos el ejemplo de lectura de archivos que vimos antes. Como mencionamos allí, si se produce un error mientras accedemos al archivo o procesamos sus datos, no se ejecutará la orden CloseFile y el archivo permanecerá abierto. Esto puede ocasionar pérdida de datos ya que el sistema de directorios se vuelve inestable. Agreguemos el código para cerrar el archivo aunque se produzca un error

var
  f: file;
  b: array[0..49 of byte];
begin
  try
    try
      AssignFile(f,’nombre’);
      ResetFile(f,1);
      BlockRead(f,b,50);
    except
      on eInOutError do
        ShowMessage(‘Error al trabajar con el archivo’);
    end;  
  finally
    CloseFile(f);
  end;
end;

Liberar memoria.
Si creamos una estructura dinámica, es nuestra responsabilidad devolver la memoria utilizada al sistema. Veamos un ejemplo utilizando GetMem y FreeMem:

type
  aInt= array[0..0] of integer;
  paInt = ^aInt;
var
  a: paInt;
begin
  {solicitamos memoria para 10000 enteros}
  GetMem(a,SizeOf(integer)*10000);

  try
    {utilización de la memoria}
  finally
    if assigned(a) then FreeMem(a,SizeOf(integer)*10000);
  end;
end;

Utilizar el objeto de la Excepción

Cuando se dispara una excepción se crea una instancia de la clase correspondiente. Por lo tanto, podemos en principio tener acceso a las propiedades y métodos de la misma. La definición de la clase Exception es la siguiente (SYSUTILS.PAS):

Exception = class(TObject)
private
  FMessage: string;
  FHelpContext: Integer;
public
  constructor Create(const Msg: string);
  constructor CreateFmt(const Msg: string; const Args: array of
              const);
  constructor CreateRes(Ident: Integer); overload;
  constructor CreateRes(ResStringRec: PResStringRec); overload;
  constructor CreateResFmt(Ident: Integer; const Args: array of
              const); overload;
  constructor CreateResFmt(ResStringRec: PResStringRec; const Args:
              array of const); overload;
  constructor CreateHelp(const Msg: string; AHelpContext: Integer);
  constructor CreateFmtHelp(const Msg: string; const Args: array of
              const; AHelpContext: Integer);
  constructor CreateResHelp(Ident: Integer; AHelpContext: Integer);
              overload;
  constructor CreateResHelp(ResStringRec: PResStringRec;
              AHelpContext: Integer); overload;
  constructor CreateResFmtHelp(ResStringRec: PResStringRec; const
              Args: array of const; AHelpContext: Integer); 
              overload;
  constructor CreateResFmtHelp(Ident: Integer; const Args: array of
              const; AHelpContext: Integer); overload;
  property HelpContext: Integer read FHelpContext write 
           FHelpContext;
  property Message: string read FMessage write FMessage;
end;

Como vemos en el listado anterior, tenemos disponibles tres propiedades: una indica el contexto de ayuda, y las otras dos dan acceso al mensaje de error en los dos formatos utilizados en Windows. Veamos cómo utilizar estas propiedades para mostrar un mensaje personalizado. Para acceder al objeto de la excepción debemos utilizar una variable temporal. La forma de hacerlo es la siguiente:

try
  {Algún proceso}
except
  on e: eDivByZero do
    ShowMessage(‘Error!’#13+e.Message);
end;

Con este código no estamos creando una nueva instancia de la excepción; únicamente definimos una variable que apunta al objeto creado por Lazarus. La clase Exception (y la mayoría de los descendientes directos) tienen pocas propiedades de utilidad, pero nada nos impide en los descendientes creados por nosotros agregar otras nuevas. Esto es precisamente lo que hacemos con la clase eInOutError, definiendo una propiedad que almacena el código de error devuelto por el sistema:

EInOutError = class(Exception)
public
  ErrorCode: Integer;
end;

Por lo tanto, podemos saber cual fue el error que provocó la excepción. El siguiente fragmento indica cómo mostrar este código al usuario, a la vez que muestra una forma de asegurar el cierre del archivo:

var
  f:file;
  st: array[0..50] of char;
begin
  try
    try
      AssignFile(f,’PRUEBA.TXT’);
      Reset(f,1);
      BlockRead(f,st,SizeOF(st));
    except
      on e: eInOutError do
        ShowMessage( format(‘Error %d al acceder al archivo' +
                     PRUEBA.TXT’, [e.ErrorCode]));
    end;
  finally
    {$I-} {desactiva detección de errores}
    CloseFile(f); {puede dar error si f no está abierto}

    {$I+} {activa la detección de errores de nuevo}
  end;
end;

Note la utilización de {$I-} y {$I+} en la parte Finally, si el archivo no se pudo abrir se produce el error, se muestra el mensaje... y se ejecuta CloseFile sobre un archivo que no está abierto, lo que genera otro error. Para evitar que la ejecución salte al bloque protegido superior con este último error, desactivamos la detección de los mismos cuando tratamos de cerrar el archivo. De esta manera si el archivo está abierto se cierra normalmente, y si no está abierto no pasa nada. 

Una aplicación más elaborada podría mantener una lista con los mensajes de error correspondientes y utilizar el código de error como índice.

Provocar una excepción

Cuando se dispara una excepción se crea una instancia de la clase correspondiente. Por lo tanto, Hay ocasiones en las que es conveniente llamar a una capa de tratamiento de errores superior. Por ejemplo, podríamos mostrar un mensaje en un bloque local y después dejar que la capa superior libere recursos. Pero una vez que hemos tratado una excepción, ésta se da por terminada y el código de los bloques protegidos superiores no se ejecuta. Debemos indicar a Pascal que deseamos mantener la excepción para que pueda tratarla el bloque superior, algo así como llamar a un método heredado desde una clase descendiente.

Para lograrlo, el lenguaje contempla la opción de relanzar la excepción, como si se volviera a
producir. Sólo debemos insertar en el código la palabra reservada raise:

try
  StrToInt(‘a45');
except
  on exception do
  begin
    ShowMessage(‘Se ha producido un error’);
    raise;
  end;
end;

En el ejemplo anterior utilizamos raise para disparar la misma excepción que estabamos procesando. La ejecución salta inmediatamente al bloque protegido anterior.

También utilizamos raise para disparar nuestras propias excepciones, como veremos a continuación.

Podríamos expresarlo de otro modo, cuando se provoca una excepción, una vez la hemos procesado con la sentencia E:Exception, la ejecución continua hacia el siguiente bloque de código. Si queremos detener la ejecución del programa debemos utilizar el comando raise:

var
  F: TextFile;
begin
  AssignFile(F, 'C:\noexiste.txt');
  ShowMessage('1');

  try
    Reset(F);
  Except
    on E: Exception do
      raise;
  end;

  ShowMessage('2');
end;

En el ejemplo anterior nunca llegaría a ejecutarse el segundo ShowMessage ya que raise detiene la ejecución del procedimiento.


Definir nuevas excepciones

Cuando se produce un error en un procedimiento nuestro (ya sea de un objeto o no), podemos tratar de recuperarnos en el lugar o bien lanzar una excepción indicando que algo anduvo mal.

Esta segunda alternativa es la preferida, porque le permite al programador tratar todos los errores del mismo modo.

Para definir una nueva excepción, debemos crear un descendiente de Exception o alguna clase más especializada. En este descendiente podemos definir nuevas propiedades o métodos de la manera usual, lo cual nos permitirá un mejor tratamiento del error.

Vayamos a la práctica. Supongamos que implementamos una función que transforma un número entero en una cadena de unos y ceros que conforman su representación binaria, pero no queremos tratar los números negativos. Podemos entonces definir una excepción nueva y dispararla cuando detectamos que el número es incorrecto. No necesitamos ninguna propiedad ni método nuevo, por lo que la excepción puede ser un descendiente directo de Exception. No obstante, como se trata en realidad de un error durante una conversión, haremos que descienda de eConvertError:

type
  eSoloPosit = class (eConvertError)
  end;

y ahora escribimos el procedimiento de conversión:

function DecToBin(v:integer): string;
var
  temps: string;
begin
  if v<0 then
  begin
    raise eSoloPosit.Create('No se permiten números negativos');
  end;

  if v>0 then 
       temps:= ''
  else temps:= '0';

  while v>0 do 
  begin
    temps:= Chr(v Mod 2+48)+temps;
    v:= v div 2;
  end;

  result:= temps;
end;

El proyecto muestra la llamada de esta función desde un bloque protegido, en una aplicación simple de conversión:

procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    Label3.caption:= DecToBin(StrToInt(edit1.text));
  except
    on e: eConvertError do
      ShowMessage('Error! '+e.Message);
  end;
end;

Note que el tratamiento jerárquico nos permite procesar también los errores de la conversión a entero con el mismo bloque.