lunes, 19 de noviembre de 2018

BinaryMessage

Si necesita serializar datos en formato binario, BinaryMessage puede ayudar.

En computación, serialización es el proceso de convertir datos abstractos en datos almacenados (en forma persistente) o datos para trasmitirlos a otro destino (servidor de red o dispositivo).

He diseñado BinaryMessage basándome en productos como protocol-buffersmsgpack; debido a la necesidad de trasmitir datos en un producto que estoy terminando, esremo, necesitaba serializar, trasmitir y de-serializar datos al recibirlos de una forma lo más automática posible.

Tal vez pueden pensar, "hey!, para que crear la rueda si ya hay xml o json?". Pues a parte de javascript que tiene un tipo de datos json, en todos los demás lenguajes hay unas clases para manejar XML o JSON, por lo general, creamos clases para manejar datos y queremos que esas clases las podamos serializar y con XML o JSON tenemos que "mapearlas" a esos tipos de objetos.

Además el tamaño de los objetos serializados son menores, pero, y por que no comprime los datos? Cuando los datos son pequeños (como los mensajes de un chat) los datos comprimidos son mayores a los datos originales. Y cuando los productos que brindamos están en servicios en la nube donde cobran el ancho de banda de entrada/salida, los bytes cuentan!

Otro punto es utilizar el editor para ayudarnos con auto-completado ya que son propiedades de clases y con XML y JSON no podemos usar auto-completar a propiedades.

Aunque he diseñado BinaryMessage (como los otros productos) para que sea plataforma-neutral (que se pueda usar en cualquier plataforma, Win, MacOS, Linux, raspberry-Pi) y lenguaje-neutral (c, c++, etc) en este momento la especificación es open y está codificado en Xojo.

Para usar BinaryMessage una clase (si no es creada con el compilador o usando la clase BinaryMessage.Parser) debe heredarse de BinaryMessage.Message adicionar attributos y usar .WriteTo a un MemoryBlock/BinaryStream para serializar y utilizar .ReadFrom desde un MemoryBlock/BinaryStream para de-serializar.


Ejemplo serializar:

Dim john As New People
john.id= 5
john.name= "John Do€"
john.email= "john@server.com"
john.address= Array("1600 Pennsylvania Avenue", "935 Pennsylvania Avenue")

Dim phone1 As New PhoneNumber
phone1.number= "123 4567"
phone1.type= 1
phone1.flags= Array(4900000000, 5000000000, 80000000000)
john.phones.Append phone1

Dim phone2 As New PhoneNumber
phone2.number= "555 5555"
phone2.type= 2
john.phones.Append phone2

Dim phone3 As New PhoneNumber
phone3.number= "999 5555"
phone3.type= 3
john.phoneX= phone3

Dim prefs As New Dictionary
prefs.Value("one")= 1
prefs.Value(2)= "two"
prefs.Value("three")= True
prefs.Value(3.5)= 49098236671
john.prefs= prefs

mbMessage= New MemoryBlock(0)

john.WriteTo mbMessage


Ejemplo de-serializar:


BinaryMessage.Message.RegisterClass GetTypeInfo(PhoneNumber)

Dim john As New People
john.ReadFrom mbMessage


Si desea más información sobre la especificación o desea probar BinaryMessage, por favor comente o escríbame.

Hasta la próxima.



miércoles, 14 de noviembre de 2018

LoggerFactory

En un reciente proyecto en el que he estado trabajando este año, necesité un buen sistema de log (bitácora o entradas en un diario), como de costumbre busqué si había alguno para Xojo/Real basic, al no encontrar uno que fuera flexíble y no "encadenado" a un particular componente o recurso, decidí crear mi propio logger: LoggerFactory.

A propósito, en computación un Logger es un software que genera mensajes y opcionalmente los almacena para reportar o analizar luego, puede encontrar información de sus inicios aquí y más aquí, un buen ejemplo de donde o para qué utilizarlo pueden ser los logs de servidores, en especial los servidores web; ellos almacenan mucha información de los clientes para después analizarlos con servicios muy comunes en web-hostings. A propósito, talvez se preguntaran: para que utilizar los logs de servidor si tengo otras herramientas como google analitics? pues esas otras herramientas funcionan sólo en navegadores de "buen comportamiento" como chrome, pero existen miles de herramientas para acceder a servidores web para sólo ver las cabeceras del archivo html o sin sus links, imágenes, javascripts, css, etc, o cuando escribe un "downloader" usando HTTPSocket.

Como regla general se deben monitorear los logs para optimizar, corregir o defenderse contra ataques, de ahi la importancia de un buen Logger.


Debería cumplir estas (entre otras) características:
  1. Fácil de usar
  2. Extensible
  3. Asincrónico
  4. Varias salidas por nivel
  5. Personalizar formato de salida

Basandome an algunos muy buenos loggers, entre otros java.util.logging y MacOS Logging cree LoggerFactory, (existe LoggerFactory para java, pero es más un wrapper (envoltorio) a otros loggers) el cual resultó ser bastante sencillo y corto de codificar, en realidad menos de 900 líneas de código, con las características que quería para mi proyecto y además me pudiera servir para cualquier otro proyecto.

Ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  Dim logger As LoggerFactory.Logger= LoggerFactory.Get(CurrentMethodName)
  '...
  logger.Info "this is a info"
  '...
  logger.Debug "this is a debug"
  '...
  logger.Trace "this is a trace"
  '...
  logger.Error "this is a error"
  '...
  logger.Warn "this is a warning"
  '...
  logger.Fatal "this is a fatal"


LoggerFactory usa estos niveles: ALL, DEBUG, ERROR, FATAL, INFO, TRACE, WARN


Por defecto, LoggerFactory está configurado para consola y System.DebugLog, Funciona sin ninguna modificación en proyectos Consola y Desktop, no he probado en web. Puede configurarse en cualquier momento de la aplicación pero lo normal es hacerlo al inicio, ejemplo:


1
2
3
  LoggerFactory.Adders.Append New LoggerFactory.ConsoleAdder(LoggerFactory.TypeLevel.ALL)

  LoggerFactory.Adders.Append New LoggerFactory.FileLogAdder(LoggerFactory.TypeLevel.ALL)

En el anterior código, he configurado un "Adder" para consola y otro para archivo, aquí es donde explico lo de Adders, Adders es la forma para "adicionar" salidas (mensajes) configurando el nivel, o si es salida a un archivo, características del archivo, por ejemplo. Los Adders básicos son:

  • ConsoleAdder: Salidas por consola (sólo en proyectos consola, obiamente)
  • SystemDebugLogAdder: El típico System.DebugLog
  • SystemLogAdder: El System.Log
  • FileLogAdder: Salidas a un archivo

Lo interesante es que podemos, por ejemplo, configurar salidas de tipo DEBUG a SystemDebugLogAdder, además salidas de tipo ERROR a un archivo, cambiar el formato de salida o inclusive crear mis propios Adders, lo único que tengo que hacer es crear una sub-clase de LoggerFactory.Adder y sobreescribir el método "Log"; con eso puedo crear una salida a, por ejemplo una base de datos o un servicio web; sólo configurando los Adders, sin cambiar nada más.


Una característica interesante de los Adders es que puedo configurar el formato de salida, así:


1
2
3
  Dim addFile As New LoggerFactory.FileLogAdder(LoggerFactory.TypeLevel.ALL)

  addFile.Format= "%d"+ SEP+ "%t"+ SEP+ "%l"+ SEP+ "%c"+ SEP+ "[%a]"+ SEP+ "%m"

Donde las etiquetas pueden ser:

%d: Date
%t: Time
%l: Level
%c: Counter
%u: SubSystem
%a: Category
%i: ID
%o: ObjectID
%e: EventType
%m: Message

Y "SEP" es una propiedad del adder, por defecto es TAB [Chr(9) en ASCII] y también se puede configurar.


Además el FileLogAdder puede ser configurado con "roll over" o re-iniciar, con la propiedad FileLogAdder.FileType y puede ser: al mismo archivo, anual, mensual, diario, por hora, o por el tamaño (por ejemplo que el archivo log sea de máximo 10M (10 megabytes) de tamaño y automáticamente asigne nombres secuenciales a los archivos)


Por último, hablemos de "Formatter", cuando escribimos mensajes, normalmete necesitamos adicionar valores de variables (números, por ejemplo) y darles un formato, por ejemplo en una variable llamada "miVariable" tenemos un valor de 1250 y queremos mostar un texto como: "Valor: 1,250", en xojo existe la función Str() o Format() y podemos asignar a una variable de tipo String, sin embargo puedo hacerlo también de la siguiente manera:


1
2
3
Dim miVariable As Single= 1250

logger.Debug "Valor: %-###,###.##f", miVariable // Salida: "Valor: 1,250"

Con la ventaja que los textos son más legibles y pueden ser almacenados en una constante para, por ejemplo, localizarlos en otro idioma.

Otros ejemplos:


1
2
3
4
5
6
  Dim n1 As UInt64= 202002021101
  Dim n2 As Double= 12.3456
  Dim n3 As Single= -12345.6789
  Dim s1 As String= "world"
  logger.Debug "i = %2d, n1= %u, n2= %3.5f, hello %s, single= %-###,##0.0###f - %{pub}s", _
  i, n1, n2, s1, n3, "test"

Las etiquetas pueden ser:

%d: Signed integers
%u: Unsigned integers
%f: Signed doubles/singles
%s: Strings


Otras características interesantes son el manejo de Sub-Sistemas y la precisión de tiempo de hasta nanosegundos, pero eso quedará para otra ocasión, si le interesa o tiene alguna sugerencia o pregunta, por favor contácteme o comente. hasta la próxima.



Gracias a Javier Mendéz por sus recomendaciones.





miércoles, 27 de mayo de 2015

Leer XML de la web y grabar en base de datos

Para algunos trabajar con archivos XML puede ser confuso y engorroso, sin embargo Xojo nos facilita mucho el trabajo con su clase XMLDocument.

He preparado un pequeño paso a paso con un ejemplo que viene con Xojo, la versión que trabajo es la 2015r21 pero funciona con versiones anteriores.
  1. En la ventana "Project Chooser" clic en Examples, Communications/Internet/HTTP Example.xojo_binary_project.
  2. Cambiar la propiedad Super de EdidFieldPlus a TextArea.
  3. Crear un Botón con el caption toDB y en evento action adicionar el código.
  4. Correr en proyecto.
  5. Cambiar la URL a "http://www.w3schools.com/xml/cd_catalog.xml" oprimir "Go".
  6. Oprmir "toDB" y revisar la baseDatos "FromXML.sqlite" del escritorio.

Código para toDB.Action:

  Dim f As FolderItem= SpecialFolder.Desktop.Child("FromXML.sqlite")
  
  Dim db As New SQLiteDatabase
  db.DatabaseFile= f
  
  If f= Nil Or Not f.Exists Then
    If db.CreateDatabaseFile Then
      Dim sql As String= "CREATE TABLE cd_catalog (title TEXT, artist TEXT, year INT);"
      db.SQLExecute(sql)
      If db.Error Then
        MsgBox "DB Error: " + db.ErrorMessage
        Return
      End If
    Else
      MsgBox "The database couldn't be created. Error: " + db.ErrorMessage
      Return
    End If
  Else
    If Not db.Connect Then
      MsgBox "The database couldn't be opened. Error: " + db.ErrorMessage
      Return
    End If
  End If
  
  Dim xml As New XmlDocument(BodyField.Text)
  
  Dim ps As SQLitePreparedStatement= db.Prepare("INSERT INTO cd_catalog (title, artist, year) VALUES (?, ?, ?)")
  ps.BindType(0, SQLitePreparedStatement.SQLITE_TEXT)
  ps.BindType(1, SQLitePreparedStatement.SQLITE_TEXT)
  ps.BindType(2, SQLitePreparedStatement.SQLITE_INTEGER)
  
  db.SQLExecute("BEGIN TRANSACTION")
  
  For i As Integer= 0 To xml.DocumentElement.ChildCount- 1
    Dim sTitle, sArtist As String
    Dim iYear As Integer
    For j As Integer= 0 To xml.DocumentElement.Child(i).ChildCount- 1
      Dim node As XmlNode= xml.DocumentElement.Child(i).Child(j)
      Select Case node.Name.Uppercase
      Case "TITLE"
        sTitle= node.Child(0).Value
      Case "ARTIST"
        sArtist= node.Child(0).Value
      Case "YEAR"
        iYear= Val(node.Child(0).Value)
      End Select
    Next
    ps.SQLExecute(sTitle, sArtist, iYear)
  Next
  
  db.Commit


Puede ver video aquí.

Espero les sirva, chao, chao.

Imágenes en BaseDatos y Xojo

Cuando se intenta obtener imágenes en bases de datos como PostgreSQL, MySQL o MSSQL desde Xojo tenemos problemas, y es que no funciona la propiedad PictureValue debido a que son bases de datos diseñadas (generalmente) para acceder sobre red, es decir, los datos se tiene que "codificar" (encode) a texto y después enviarlo a la base de datos; por el contrario una base de datos como SQLite los datos son binarios.

Si ud. almacena las imágenes desde xojo, el siguiente código funciona en PostgreSQL, MySQL, MSSQL y ODBC:

Para almacenar:

  ' Image.png, db, la tabla tbl_images y el registro con id=1 deben existir.
  ' el campo image debe ser tipo bytea (postgres) o text

  Dim p As Picture= SpecialFolder.Desktop.Child("image.png")
  Dim mb As MemoryBlock= p.GetData(Picture.FormatPNG)
  Dim s As String= EncodeBase64(mb.StringValue(0, mb.Size))

  Dim sql As String= "UPDATE tbl_images SET image= '"+ s+ "' WHERE id=1"
  db.SQLExecute(sql)

  If db.Error Then
    System.Log(System.LogLevelError, "PostgreSQL(DB:"+ db.DatabaseName+ _
    ",U:"+ db.UserName+ "):"+ EndOfLine+ EndOfLine+ sql)
    Return
  End If

Para obtener:

  Dim rs As RecordSet= db.SQLSelect("SELECT * FROM tbl_images WHERE id=1")

  mb= DecodeBase64(DecodeHex(rs.Field("image").StringValue))
  p= Picture.FromData(mb)

  'Canvas1 debe existir
  If p<> Nil Then Canvas1.Backdrop= p


Si ud NO almacena las imágenes desde xojo, bueno, las cosas son un poco más difícil, para obtener imágenes desde bases de datos se tiene que hacer las operaciones inversas de las que cuales fueron almacenadas, por ejemplo en PosrgreSQL para almacenar imágenes en campos bytea puede usarse:

En PHP usando escape_byea:

<?php
  // Connectar a la base de datos
  $dbconn = pg_connect('dbname=foo');

  // Leer en un fichero binario
  $data = file_get_contents('image1.jpg');

  // Escapar el dato binario
  $escaped = pg_escape_bytea($data);

  // Insertarlo en la base de datos
  pg_query("INSERT INTO gallery (name, data) VALUES ('Pine trees', '{$escaped}')");?>
En PHP usando bin2hex:

<?php
  // Connect to the database
  $dbconn = pg_connect( 'dbname=foo' );
  
  // Read in a binary file
  $data = file_get_contents( 'image1.jpg' );
  
  // Escape the binary data
  $escaped = bin2hex( $data );
  
  // Insert it into the database
  pg_query( "INSERT INTO gallery (name, data) VALUES ('Pine trees', decode('{$escaped}' , 'hex'))" );

  // Get the bytea data
  $res = pg_query("SELECT encode(data, 'base64') AS data FROM gallery WHERE name='Pine trees'");  
  $raw = pg_fetch_result($res, 'data');
  
  // Convert to binary and send to the browser
  header('Content-type: image/jpeg');
  echo base64_decode($raw);
?>
En PHP usando lo_import:
<?php
 $uploaddir = '/home/postgres/';
 $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
 $name = $_POST['name'];

 if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile))
 {   // echo "File is valid, and was successfully uploaded.\n";
 }
 else   {   echo "File size greater than 300kb!\n\n";   }

 echo "'$name'\n";

 $conn = pg_pconnect("dbname=test user=postgres password=postgres");
 $query = "insert into image values ('$name', lo_import('$uploadfile'), 'now')";
 $result = pg_query($query);

 if($result)
 {
     echo "File is valid, and was successfully uploaded.\n";
     unlink($uploadfile);
 }
 else
 {
     echo "Filename already exists. Use another filename. Enter all the values.";
     unlink($uploadfile);
 }
 pg_close($conn);
 ?>

Y en otras bases de datos sucede algo similar, no hay un estandar.Para obtener se debe utilizar DecodeBase64 o DecodeHex (o los dos) para llevar el valor del campo a una variable MemoryBlock y utilizar Picture.FromData para convertirlo en imagen.

sábado, 8 de febrero de 2014

Lo nuevo en DBReport

Hola y Feliz 2014!

Hace mucho tiempo no actualizo este blog, mantengo más actualizado la versión en inglés así que aquí va un resumen:

La última versión la puede bajar aquí (o aquí).


Nuevo! Render a PDF:


Render es la palabra que uso para "imprimir" a un archivo PDF, la uso en vez de "Save" porque la quiero distinguir de "Grabar" la definición del reporte que en estos momentos se hace en XML (SaveXML) pero en el futuro puedo cambiar a otro formato por ejemplo a JSON lo que crearía un procedimiento (SaveJSON) el cual grabaría la definición del reporte en formato JSON.

Código ejemplo:

Dim rpt As New DBReport(f, rs)
Dim fPDF as FolderItem= SpecialFolder.Desktop.Child( “DBReportExample2.pdf” )
rpt.RenderToPDF fPDF
fPDF.Launch


Unable to display content. Adobe Flash is required.


Nuevo! SubReports:

A veces es necesario inprimir cosas que no es fácil teniendo un sólo Recordset, así que se necesita "adicionar" Recordsets y usarlo en el reporte, aquí es útil subreports.

Primero cree un reporte que sirva te "Plantilla", no es necesario pero es recomendable, Normalmente sólo contiene una banda: Detalle, sinembargo puede adicionar otras bandas, grábelo como un reporte normal.

Cargue o cree el reporte principal, Arrastre y suelte un elemento SubReport a la banda (o clic derecho en la banda, nuevo elemento, Subreport). El nuevo objeto asume todo el ancho de la banda, pero se puede cambiar el tamaño. La altura es automáticamente expandida sie es necesaria cuando se imprime.

Defina la propiedad "Plantilla" desde el panel propiedades (ya no se necesita el archivo plantilla), puede cambiar el subreport haciendo clic en Subreport del arbol en el panel esquema (botón).

Por último debe asignar el recordset al subreport antes de llamar al Diseñador o al imprimir con la instrucción:

rpt.SubReport(“NameOfSubReport”).RecordSet= rs

El "NameOfSubreport" debe ser el mismo de la propiedad "Nombre"

Unable to display content. Adobe Flash is required.



Consulte/adicione al "issue tracker" en: https://bitbucket.org/lbmonsalve/dbreport/issues


Gracias y hasta la próxima!

sábado, 31 de agosto de 2013

DBReport para RealBasic (Xojo) IV

El tema de hoy son Gráficas (charts), usando gráficas en el reporte.

Las gráficas (charts) son representaciones de datos numéricos mediante recursos gráficos (vease wikipedia). Vale la pena aclarar que podemos utilizar gráficas de librerías avanzadas como las de MonkeyBread Chart Director o las que vienen con DBReport q son muy básicas y de tipo Barras, Líneas o Circular.

Hay dos formas de usar gráficas: Crear la gráfica y enviar imágenes como
parámetros o crear la gráfica en el diseñador; en el sólo podemos utilizar las
gráficas q vienen con el DBreport la cual es la clase DBReportChart.

Este es un ejemplo de cómo utilizar gráficas en DBReport:

Unable to display content. Adobe Flash is required.

La información se proporciona "como-está". Todas las marcas, logos, etc pertenecen a sus respectivos dueños.

martes, 23 de julio de 2013

DBReport para Realbasic (III)

El tema son fórmulas, como utilizar fórmulas en el reporte.

Las fórmulas son etiquetas que tienen operaciones, por lo general aritméticas, pero puede hacerse para cadenas. Se necesitan fórmulas para ciertos campos que no están en el recorset como por ejemplo:

Un RecordSet tiene Cantidades y ValorUnitario, pero no tiene subtotal, entonces se crea una etiqueta y en la propiedad valor, clic en el triángulo, clic en Editor de fórmulas y se escribe este código: "SetValue Cantidades* ValorUnitario".

El editor utiliza cualquier expresión RBScript para su uso.

En es siguente video explico un poco más:


Unable to display content. Adobe Flash is required.

La información se proporciona "como-está". Todas las marcas, logos, etc pertenecen a sus respectivos dueños.