Weblogs Código

Una sinfonía en C#

¿Qué es TDD?

octubre 20, 2017 02:16

En Septiembre pasado tuve el honor de dar una charla en Madrid para al comunidad de MSCoders sobre TDD y este post es un poco el resumen de lo expuesto en la misma, que es una mezcla entre lo que la teoría dice del tema muuuyy matizado con mi experiencia y opiniones.

Al principio voy a contar un poco lo de la charla y al final del post el ejemplo, así que si asististe a la charla o sabés del tema te podés saltar todo e ir directo al código.

¿Qué es TDD?

Son las siglas de Test Driven Development, es decir, desarrollo guiado por pruebas, la idea es desarrollar nuestro software comenzando por las pruebas, ya que en principio, sabemos lo que queremos que el software haga, lo que no sabemos es el cómo.

Entonces primero planteamos la prueba que va a validar que nuestro código cumpla con una tarea dada, luego creamos el código que hacer efectivamente la tarea.

Desarrollar software es complejo.

Todos sabemos que el software es complejo, es complejo en sí la tarea de “hacer” el software y es complejo todo lo que lo rodea, los requerimientos que cambian, que son poco claro, que no están del todo definidos y en general el escenario que nos encontramos es una combinación de todo eso, a eso le podemos sumar todo lo inherente al desarrollo, los patrones, las prácticas, las cosas que hay que tener en cuenta para futuras funcionalidades, la limitaciones del lenguaje, las ambigüedades, los casos no planteados, la imposibilidad de comprenderlo todo a la primera, etc.

La agilidad y sus ciclos de feedback

En las metodologías ágiles (como Scrum) la idea es que habiendo mucho que no sabemos la mejor forma de abordarlo es con ciclos de trabajo cortos durante los cuales entregamos algo de valor al final de cada ciclo para obtener una retro-alimentación que nos permita ajustar lo que hacemos y validar los requerimientos, el siguiente ciclo veremos si necesitamos cambiar algo que hicimos o está suficientemente bien, de este modo vamos aprendiendo cada vez más y vamos teniendo siempre algo que mostrar para obtener información.

En cierta forma TDD es similar, la idea es comenzar con las pruebas para ir “descubriendo” el código, en lo relacionado al diseño, y al final de cada test tener una caso funcionando (que es mejorable, por supuesto) pero tenemos feedback que nos permite comprender mejor el problema, la solución y nos da siempre algo que funciona.

El ciclo de TDD

Una de las más importantes de TDD es que el ciclo, que consta de tres partes:

  • Escribir el test y que falle = ROJO
  • Escribir que código que hace que el test pase (cumplir con el requerimiento) = VERDE
  • Hacer refactor (cambiar el código sin que cambie su comportamiento) = AZUL

La idea es centrarnos en una tarea, que funcione, sin ser perfecta la implementación, de este modo evitamos varios de los tantos males del desarrollo de software como ser:

  • Optimizar antes de tiempo
  • Hacer cosas que uno no va a necesitar

El objetivo es hacer solo que es necesario para cumplir con el requerimiento, ni un poco más ni un poco menos, en definitiva es por lo que el cliente se pone contento, que al presionar un botón algo ocurra.

Además nosotros no nos desenfocamos con optimizaciones que no son necesarias ahora mismo (y tal vez nunca lo sean) o por intentar reutilizar código que nunca será reutilizado o por implementar ciertos escenarios que nunca ocurrirán, en resumen, somos más eficientes y entregamos valor al final de cada ciclo que nos permite evolucionar.

El ciclo más o menos es ese gráfico simplificado en los pasos 1, 2 y 5.

Un ejemplo de TDD

Para ejemplificar esto voy a hacerlo a través del código, con un ejercicio muy simple: el String calculador, y como todo tiene una lista de requerimientos:

  1. Create a simple String calculator with a method int Add(string numbers)
    1. The method can take 0, 1 or 2 numbers, and will return their sum (for an empty string it will return 0) for example “” or “1” or “1,2”
    2. Start with the simplest test case of an empty string and move to 1 and two numbers
    3. Remember to solve things as simply as possible so that you force yourself to write tests you did not think about
    4. Remember to refactor after each passing test
  2. Allow the Add method to handle an unknown amount of numbers
  3. Allow the Add method to handle new lines between numbers (instead of commas).
    1. the following input is ok:  “1\n2,3”  (will equal 6)
    2. the following input is NOT ok:  “1,\n” (not need to prove it - just clarifying)
  4. Support different delimiters
    1. to change a delimiter, the beginning of the string will contain a separate line that looks like this:   “/1/[delimiter]\n[numbers…]” for example “//;\n1;2” should return three where the default delimiter is ‘;’ .
    2. the first line is optional. all existing scenarios should still be supported
  5. Calling Add with a negative number will throw an exception “negatives not allowed” - and the negative that was passed.if there are multiple negatives, show all of them in the exception message

Hay algunos más, si quieren verlos este es el link al post original de Roy Osherove

Comenzando con TDD en el código

Vamos a ser totalmente minimalistas con la implementación (como todo en la vida hay gente más y menos extremista, me gusta un poco por el medio pero en este caso como es el primer ejemplo voy a ser más extremista que de costumbre), así que en Visual Studio creamos un proyecto del tipo de Unit Test, y a la clase que nos crea por defecto le vamos a poner un nombre relacionado con lo que estamos haciendo para esto del nombre hay varias opciones:

  • Que se llame StringCalculatorTests
  • Que se llame StringCalculatorFixture
  • Que se llame StringCalculatorShould
  • Etc.

No vamos a discutir ahora por qué una puede ser mejor que otra, vamos a ponerle StringCalculatorShould, y para comenzar con el primer test vamos a tomar el primer requerimiento, ya habrá tiempo de cambiar el nombre a la clase si no nos gusta:

namespace UnitTestProject
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class StringCalculatorShould
    {
    }
}

1.1: Crear un StringCalculador con un método Add que acepte 0, 1 o 2 argumentos y retorne su suma(para un string vacío retornará 0) por ejemplo “” o “1” o “1,2”

Ok, en este caso hay más de un escenario, además de lo más obvio “crear un StrincCalculador con un método Add” nos da 3 escenarios:

  • Al recibir vacío retornará 0
  • Al recibir un argumento retornará el mismo número
  • Al recibir dos números retornará la suma

Entonces, vamos a comenzar con el primero:

“Al recibir vacío retorna 0”

Primero: Escribir el test

Lo primero es definir el test sin la implementación, es decir, del flujo de testing hacer que el test se ponga en rojo.

namespace UnitTestProject
{
    using System;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class StringCalculatorShould
    {
        [TestMethod]
        public void Return_0_When_Receives_Empty()
        {
            var stringCalculator = new StringCalculator();
            var result = stringCalculator.Add("");
            Assert.AreEqual(0, result);
        }
    }

    internal class StringCalculator
    {
        internal object Add(string v)
        {
            throw new NotImplementedException();
        }
    }
}

Vemos varias cosas para señalar: la clase StringCalculator está en el mismo archivo, el parámetro se llama “v”  y las visibilidad de la clase y el método Add es al menos discutible, pero ignoremos todo esto de momento, sigamos adelante, todas estas cosas las vemos cuando llegue el momento del refactor.

Segundo: Hacer que el test pase

Luego hacemos la implementación (el código que hace que el test pase) pero lo más simple posible, es decir, que se ponga verde.

internal class StringCalculator
{
    internal object Add(string v)
    {
        return 0;
    }
}

Sí, es obvio que retornará 0 en éste y en todos los casos, pero está bien, no pensemos en los otros casos, solo en éste, no queremos optimizar antes de tiempo solo queremos cumplir con el caso y con esto es suficiente.

Tercero: Refactor

Una vez que tenemos el test pasando en verde llega el momento del refactor, el refactor no tiene sentido sin test que aseguren que no “rompimos” la funcionalidad, con lo cual nunca vamos a hacer refactor de algo que no tiene test; de modo que ahora que tenemos algo de cobertura de test hacemos refactor.

image


Perfecto, movimos la clase a un archivo propio y tenemos nuestro primer ciclo de TDD completo, sigamos con el siguiente caso.

“Al recibir un argumento retornará el mismo número”

Una vez más nos dedicamos a la implementación más simple, una podría llegar a pensar en qué pasaría si en lugar de un número se ingresara una letra, no? bien, en teoría es un caso que no está planteado así que no nos lo consideraremos ahora, si bien es bueno escribirlo como un caso en alguna parte para no olvidarlo o incluso plantear el nombre del test. De hecho si somos un poco listos nos damos cuenta que hay varios casos que se nos van ocurriendo que no están planteados, por ejemplo si en lugar de un string vacío llega null, o un espacio, lo mismo, lo anotamos o planteamos el test, pero ahora no invertimos esfuerzo en eso.

Bien, entonces implementamos el segundo requerimiento y nos topamos con la implementación del primero, no hay problema, tenemos tests, modificamos el código para considerar esto y ya, como siempre, la implementación más sencilla que funcione, no nos extendemos en nada que no necesitemos ahora (YAGNI)

Una vez que los test están todos en verde, llega el momento del refactor una vez más

image

“al recibir dos números retorna la suma”

Evidentemente, viendo el ejemplo, se plantea que los números se separen con comas, no nos adelantemos a sacar la coma a una variable ni nada de eso, eso lo hacemos en tiempo de refactor o tal vez nunca, ya lo veremos.

image

y continuamos así con el resto de los casos

Algunas reflexiones

IR despacio

Es muy importante hacer lo mínimo necesario para cada caso, no hace nada de nada de posibles casos o escenario, si los encontramos podemos anotarlos o escribir el nombre de los test para no olvidarlo y lo veremos más adelante, se trata de cumplir con los requisitos siendo eficientes con los recursos.

Dejar lo que no suma para más adelante

A todos nos gusta usar patrones y buenas prácticas, todo eso lo podemos hacer con TDD, pero una vez que tenemos test y una cantidad de casos que permitan descubrir si hay algo que reaprovechar o un patrón que usar

Evitar la sobre-arquitectura

Una de las consecuencias es que no nos excedemos con la arquitectura que no vamos a necesitar, algo que a muchos nos ha pasado y nos pasa.

Foco

Una de las ideas del TDD es plantearse una única cosa sencilla en su mínima expresión y nada más, esto nos permite enfocarnos en objetivos alcanzables, mejorando nuestra sensación de que estamos avanzando, mejora nuestra autoestima, nos sentimos productivos, vamos comprendiendo mejor el negocio y todo el tiempo tenemos algo que funciona, esto mejora la productividad y el ánimo.

Podemos hacer TDD o usar algunas de sus prácticas en sistemas legacy?

La respuesta es…depende, como todo en el software, si tenemos muchas dependencias indudablemente tendremos que hacer algo de refactor primero para poder usar fakes o mocks de esas dependencias en nuestros test, para esto es indispensable tener tests primero.

Hacer refactor de código legado para poder construir sobre él de una mejor manera es todo un arte, hay muchas cosas que podemos hacer, pero eso es tema de otro post.

Practicar y practicar más

Me gusta mucho poner el ejemplo del tenis, un tenista de elite (y casi todos) si bien sabe pegar el golpe de revés correctamente, lo practica, incluso más, hay ocasiones en que lo ejecuta de manera diferente dependiendo del tipo de superficie o mejor aún, cambia la técnica para mejorar su juego; mi opinión es que si uno quiere ser un programador de nivel mejor que la media debe hacer lo mismo: practicar las técnicas que utiliza a diario, en este caso TDD hay que practicarlo todo el tiempo hasta que la ejecución nos resulte natural e intuitiva.

Nos leemos.

» Leer más, comentarios, etc...

Navegapolis

Scrum no calcula el avance midiendo lo que se ha hecho, sino lo que falta

octubre 17, 2017 07:48

no ganttHay dos estrategias habituales para planificar y seguir el avance de un proyecto, que no se deben emplear en scrum.

La primera es calcular la duración de las tareas empleando unidades de tiempo concretas para predecir la ejecución como si fuera un proceso, y dibujarla en un diagrama de Gantt.
Esta estrategia es coherente para planificar trabajos "mecánicos": trabajos compuestos por actividades de ejecución procedimentada, pero no es adecuada para trabajos del conocimiento ni para trabajos creativos.

novela gantt

 

 

» Leer más, comentarios, etc...

Variable not found

Tareas en segundo plano en ASP.NET Core con IHostedService

octubre 17, 2017 06:55

ASP.NET CoreEs relativamente frecuente encontrar aplicaciones en las que necesitamos disponer de un proceso en background ejecutándose de forma continua. Hace poco hablábamos de IApplicationLifetime, un servicio del framework que nos permitía introducir código personalizado al iniciar y detener las aplicaciones, y probablemente habréis pensado que éste sería un buen sitio para gestionar el inicio y finalización de estas tareas en segundo plano.

Y aunque es posible, ASP.NET Core proporciona otras fórmulas más apropiadas para conseguirlo: los hosted services. Mediante este mecanismo, podemos crear servicios que serán gestionados por el host, y que serán iniciados y finalizados automáticamente junto con la aplicación.

Servicios alojados con IHostedService

Cogs, Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome [CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia CommonsUn hosted service es una clase que implementa el interfaz IHostedService, definido en el espacio de nombres Microsoft.Extensions.Hosting de la siguiente forma:
public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
El método StartAsync() será invocado por el host cuando esté listo para iniciar el servicio. StopAsync(), en cambio, será ejecutado cuando la aplicación esté finalizando, con objeto de que podamos hacer un cierre civilizado del mismo, por ejemplo, liberando recursos utilizados, eliminando temporales, etc.

El token que se recibe como argumento en StartAsync() se activará cuando la tarea deba finalizar porque el host va a detener la aplicación, y justo antes de llamar a StopAsync(). En este último caso, el token simplemente indica si la propia tarea de finalización debe ser cancelada; por defecto, el sistema dará unos cinco segundos antes de activar este token, por lo que más o menos será ese el tiempo máximo que tenemos para finalizar la tarea y dejar todo bien limpito.

Veamos una posible implementación de un servicio de este tipo, que simplemente registra cada segundo un contador en el log del sistema:
public class MyBackgroundService : IHostedService
{
private readonly ILogger<MyBackgroundService> _logger;

public MyBackgroundService(ILogger<MyBackgroundService> logger)
{
_logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Starting my service...");
for (var i = 1; !cancellationToken.IsCancellationRequested; i++)
{
_logger.LogInformation($"Loop #{i}");
await Task.Delay(1000);
}
}

public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Stopping my service...");
return Task.CompletedTask;
}
}
Una vez tenemos nuestra implementación del servicio, el siguiente paso para ponerlo en funcionamiento es registrar en el contenedor de dependencias una instancia de nuestra clase, asociada al interfaz IHostedService, como sigue:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IHostedService, MyBackgroundService>();
}
...
}
Cuando la aplicación ASP.NET Core arranca, el framework buscará en el contenedor todas las clases asociadas a IHostedService, las instanciará e invocará su método StartAwsync(). Es decir, de esta forma tan sencilla podemos añadir tantos servicios en segundo plano como necesitemos en nuestras aplicaciones.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 296

octubre 16, 2017 10:33

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Cross-platform

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Obtener información de la pila de las excepciones

octubre 15, 2017 10:15

Java

Las excepciones son un mecanismo incorporado en algunos lenguajes como Java para el manejo de errores y condiciones de error. En la implementación de las excepciones en los lenguajes hay diferencias, por ejemplo, en Java hay excepciones checked y uncheked y en lenguajes como C# o Groovy todas las excepciones son consideradas unchecked. En cualquier caso son una mejor forma de forzar a gestionar las condiciones de error que se producen que el comprobar no obligatoriamente el valor de retorno de una función, incluso JavaScript incorpora excepciones.

Las palabras reservadas en Java para el manejo de excepciones son try, catch , finally, throw y throws. El manejo de algunas excepciones consiste en emitir su pila de llamadas o stacktrace en la terminal o en el sistema de logging. El stacktrace contiene un mensaje de error, los métodos de la pila de llamadas del thread que la causó junto con el número de la línea. Además, las excepciones puede tener asociada una excepción causa por ejemplo un SQLException puede ser causado por un IOException por fallo de comunicación con el servidor de base de datos.

Todas las excepciones en Java heredan de Throwable y entre los métodos que tiene esta clase está getStackTrace() que devuelve un array de StackTraceElement ordenado del último método llamado al primero. Con los métodos de la clase StackTraceElement obtenemos el nombre de la clase, el archivo, el nombre del método y la linea de código de esa llamada.

Con esta información podemos imprimir en la terminal un informe de excepción diferente del que genera el método printStackTrace(). En el ejemplo limitando el informe de la pila de llamadas a los tres últimos métodos del stacktrace.

Si en una aplicación manejamos varios hilos con Thread.getAllStackTraces() obtenemos las pilas de llamadas de todos los hilos y con Thread.getStackTrace() el del hilo en concreto que con Thread.currentThread() sería el actual. Con el array de StackTraceElement obtenidos de los hilos podemos obtener un informe personalizado y la situación de cada uno, el método dumpStack() genera el stacktrace en la salida de error.

Entre las novedades de Java 9 está la clase StackWalker para procesar los elementos de la pila del thread actual usando streams y funciones lambda.

» Leer más, comentarios, etc...

Blog Bitix

Las excepciones del lenguaje Java

octubre 15, 2017 09:30

Java

Las excepciones son un mecanismo para capturar y producir condiciones de error en un programa. Es una alternativa al retorno de valores especiales que indique errores y que en estos no son obligatorios su correcta comprobación.

En Java las excepciones están incorporadas en el lenguaje desde la primera versión y que en posteriores se han mejorado. Las palabras reservadas del lenguaje para las excepciones son try, catch, finally para la captura y throw y throws para lanzar excepciones en los métodos.

Las excepciones son objetos que extienden la clase Throwable de la que en el JDK es extendida por la clase Error y Exception de la que de esta última a su vez hereda RuntimeException. Entre las comprobaciones que realiza el compilador está que las excepciones checked lanzadas por un método son capturadas por el código que lo llama.

Solo los objetos que hereden de Throwable pueden ser lanzados y capturados en los bloques try-catch. Las excepciones que heredan de Error son empleadas para casos en los que se han agotado recursos del sistema como la memoria o condiciones de error en el sistema que generalmente impiden el correcto funcionamiento del programa y de difícil tratamiento salvo terminar la ejecución. Las excepciones que heredan de RuntimeExcepcion también se les conoce como uncheked exception y no necesitan declararse en los métodos para ser lanzadas, son empleadas para advertir de errores de programación como dividir por cero produciendo ArithmeticException, desreferenciar un puntero nulo produciendo NullPointerException o acceder a una posición inválida de una array produciendo ArrayIndexOutOfBoundsException. Las excepciones que heredan de Exception pero no de RuntimeException se denominan cheked exceptions y han declararse en los métodos siendo de obligada captura o relanzado para su tratamiento en el método anterior en la pila de llamadas.

Este sería un ejemplo de código que hace uso de una excepción propia para detectar una condición de error mostrando una excepción checked y unchecked.

Aunque las excepciones son un buen mecanismo para el tratamiento de errores se les critica que rompen el flujo de ejecución de un programa y tienen un coste en rendimiento, aunque esta penalización de rendimiento en la mayoría de programas es irrelevante. Algunas recomendaciones que se hace para usar de forma efectiva las excepciones son:

  • Las excepciones no deben reemplazar comprobaciones simples con sentencias if.
  • No se deben microgestionar las excepciones. Los bloques try-catch deben contener bloques de código de varias líneas de código en vez una única sentencia por cada bloque try-catch.
  • Se debe hacer un buen uso de la jerarquía de excepciones y capturar la excepción que se vaya a tratar en el bloque catch. Una excepción se puede convertir en otra por ejemplo convertir un NumberFormatException a IOException.
  • No se deben capturar excepciones para no hacer nada con ellas, esto es no debe haber bloques catch vacíos.
  • En algunos casos es mejor lanzar una excepción que un valor null que posiblemente produzca un NullPointerException en otra parte distante del código de dónde se devolvió el valor null.
  • Propagar excepciones no es un signo de poca sabiduría, puede haber motivos para ello sobre todo si no se le puede dar un tratamiento adecuado.

Cuando se produce una excepción la clase Throwable posee métodos para emitir en la salida un informe de la pila de llamadas, también se puede personalizar la salida. Esta información es esencial y muy útil para conocer la causa de un error ya que indica entre otras cosas cada uno de los métodos, línea en el código fuente y clases donde se ha producido la excepción.

» Leer más, comentarios, etc...

Adrianistán

Interfaces gráficas multiplataforma en C# con .NET Core y Avalonia

octubre 13, 2017 11:53

Microsoft sorprendió a todos con la publicación de .NET Core el 27 de junio de 2016. Por primera vez, la plaatforma .NET se volvía multiplataforma, con soporte a macOS y GNU/Linux. Y además se volvía software libre, con licencia MIT y con un desarrollo transparente donde cualquiera puede proponer mejoras, subir parches, etc…

Esto posibilita tener soporte de primer nivel para uno de los lenguajes mejor diseñados actualmente, C#, que hasta entonces tenía un soporte de segunda en Linux, a través de Mono.

Inicialmente el soporte de Microsoft a .NET Core abarca: aplicaciones de consola, aplicaciones web ASP.NET y UWP. Mucha gente se desanimó por no tener WinForms o WPF en .NET Core. Sin embargo eso no quiera decir que no se puedan hacer interfaces gráficas en .NET Core. Aquí os presento la librería Avalonia, que aunque está en beta todavía, funciona sorprendentemente bien y en un futuro dará mucho de que hablar.

Avalonia funciona en Windows, macOS, GNU/Linux, Android e iOS. Usa XAML para las interfaces y su diseño se asemeja a WPF, aunque se han hecho cambios aprovechando características más potentes de C#. Avalonia no es WPF multiplataforma, es mejor.

Creando un proyecto

En primer lugar, tenemos que instalar .NET Core 2.0. Se descarga en la página oficial. A continuación comprobamos que se ha instalado correctamente con:

dotnet --version

Creamos ahora un nuevo proyecto de tipo consola con new

dotnet new console -o GitHubRepos

Si todo va bien nos saldrá algo parecido a esto:

Se nos habrán generado una carpeta obj y dos archivos: Program.cs y GitHubRepos.csproj. El primero es el punto de entrada de nuestro programa, el otro es el fichero de proyecto de C#.

Vamos a probar que todo está en orden compilando el proyecto.

dotnet run

Añadiendo Avalonia

Vamos a añadir ahora Avalonia. Instalar dependencias antes en C# era algo complicado. Ahora es muy sencillo, gracias a la integración de dotnet con NuGet.

dotnet add package Avalonia
dotnet add package Avalonia.Win32
dotnet add package Avalonia.Skia.Desktop
dotnet add package Avalonia.Gtk3

¡Ya no tenemos que hacer nada más! Nuestra aplicación será compatible ya con Windows y GNU/Linux. Para el resto de plataformas, hay más paquetes disponibles en NuGet, sin embargo desconozco su grado de funcionamiento. Solo he probado la librería en Windows 7, Windows 10, Ubuntu y Debian.

Program.cs

Program.cs define el punto de entrada a la aplicación. Aquí en una aplicación Avalonia podemos dejarlo tan simple como esto:

using System;
using Avalonia;

namespace GitHubRepos
{
    class Program
    {
        static void Main(string[] args)
        {
            AppBuilder
                .Configure<App>()
                .UsePlatformDetect()
                .Start<MainWindow>();
        }
    }
}

Básicamente viene a decir que arranque una aplicación Avalonia definida en la clase App con la ventana MainWindow. A continuación vamos a definir esas clases.

App.cs y App.xaml

En Avalonia tenemos que definir una clase que represente a la aplicación, normalmente la llamaremos App. Crea un fichero llamado App.cs en la misma carpeta de Program.cs, con un contenido tan simple como este:

using Avalonia;
using Avalonia.Markup.Xaml;

namespace GitHubRepos
{
    public class App : Application
    {
        public override void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }
    }
}

Lo que hacemos aquí es pedir al intérprete de XAML que lea App.xaml simplemente y por lo demás, es una mera clase hija de Application.

El fichero App.xaml contiene definiciones XAML que se aplican a todo el programa, como el estilo visual:

<Application xmlns="https://github.com/avaloniaui">
  <Application.Styles>
    <StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
    <StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
  </Application.Styles>
</Application>

MainWindow.cs y MainWindow.xaml

Ahora nos toca definir una clase para la ventana principal de la aplicación. El código para empezar es extremadamente simple también:

using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace GitHubRepos
{
    public class MainWindow : Window
    {
        public MainWindow()
        {
            Initialize();
        }
        private void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }
    }
}

Aquí hacemos lo mismo que con App.cs, mandamos cargar el fichero XAML. Este fichero XAML contiene los widgets que va a llevar la ventana. Parecido a HTML, QML de Qt, Glade de GTK o FXML de Java o XUL de Mozilla.

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="GitHub Repositories">
        <StackPanel HorizontalAlignment="Center">
                <Button Content="¡Hola mundo!"></Button>
        </StackPanel>
</Window>

GitHubRepos.csproj

Antes de poder compilar es necesario modificar el fichero de proyecto para incluir los ficheros XAML en el binario. Se trata de añadir un nuevo ItemGroup y dentro de él un EmbeddedResource.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Avalonia" Version="0.5.1" />
    <PackageReference Include="Avalonia.Skia.Desktop" Version="0.5.1" />
    <PackageReference Include="Avalonia.Win32" Version="0.5.1" />
  </ItemGroup>
  <!-- HE AÑADIDO ESTO -->
  <ItemGroup>
    <EmbeddedResource Include="**\*.xaml">
      <SubType>Designer</SubType>
    </EmbeddedResource>
  </ItemGroup>
  <!-- HASTA AQUÍ -->
</Project>

Ahora ya podemos compilar con dotnet run.

Nos deberá salir algo como esto:

Añadiendo clicks

Vamos a darle vidilla a la aplicación añadiendo eventos. Para ello primero hay que darle un nombre al botón, un ID. Usamos Name en XAML. También usaremos un TextBlock para representar texto.

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="GitHub Repositories">
        <StackPanel HorizontalAlignment="Center">
                <Button Name="lanzar" Content="Lanzar dado"></Button>
                <TextBlock Name="resultado">No has lanzado el dado todavía</TextBlock>
        </StackPanel>
</Window>

Ahora en MainWindow.cs, en el constructor, podemos obtener referencia a los objetos XAML con Find.

using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using System;

namespace GitHubRepos
{
    public class MainWindow : Window
    {
        Button lanzar;
        TextBlock resultado;
        public MainWindow()
        {
            Initialize();

            lanzar = this.Find<Button>("lanzar");
            lanzar.Click += LanzarDado;

            resultado = this.Find<TextBlock>("resultado");
        }
        private void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }

        private void LanzarDado(object sender, RoutedEventArgs e)
        {
            var r = new Random().Next(0,6) + 1;
            resultado.Text = $"Dado: {r}";   
        }
    }
}

Y ya estaría. También comentar que la función LanzarDado puede ser async si nos conviene. A partir de ahora ya puedes sumergirte en el código de Avalonia (¡porque documentación todavía no hay!) y experimentar por tu cuenta.

Un ejemplo real, GitHubRepos

Ahora os voy a enseñar un ejemplo real de la comodidad que supone usar Avalononia con .NET Core. He aquí un pequeño programa que obtiene la lista de repositorios de un usuario y los muestra por pantalla.

using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Collections.Generic;
using System.Runtime.Serialization.Json;
using System.Linq;

namespace GitHubRepos
{
    public class MainWindow : Window
    {
        Button refresh;
        TextBox username;
        TextBlock status;
        ListBox repos;
        public MainWindow()
        {
            Initialize();

            refresh = this.Find<Button>("refresh");
            refresh.Click += RefreshList;

            username = this.Find<TextBox>("username");
            status = this.Find<TextBlock>("status");
            repos = this.Find<ListBox>("repos");
        }
        private void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }

        private async void RefreshList(object sender, RoutedEventArgs e)
        {
            var user = username.Text;
            status.Text = $"Obteniendo repositorios de {user}";
            var client = new HttpClient();
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
            client.DefaultRequestHeaders.Add("User-Agent", "GitHubRepos - Avalonia");
            try{
                var downloadTask = client.GetStreamAsync($"https://api.github.com/users/{user}/repos");

                var serializer = new DataContractJsonSerializer(typeof(List<GitHubRepo>));
                var repoList = serializer.ReadObject(await downloadTask) as List<GitHubRepo>;

                repos.Items = repoList.OrderByDescending(t => t.Stars).Select(repo => {
                    var item = new ListBoxItem();
                    item.Content=$"{repo.Name} - {repo.Language} - {repo.Stars}";
                    return item;
                });
                status.Text = $"Repositorios de {user} cargados";
            }catch(HttpRequestException){
                status.Text = "Hubo un error en la petición HTTP";
            }catch(System.Runtime.Serialization.SerializationException){
                status.Text = "El fichero JSON es incorrecto";
            }
        }
    }
}

Y este es su correspondiente XAML:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="GitHub Repositories"
        Width="300"
        Height="500">
        <Grid>
                <Grid.RowDefinitions>
                        <RowDefinition Height="50"/>
                        <RowDefinition />
                </Grid.RowDefinitions>
        
                <StackPanel Grid.Row="0" HorizontalAlignment="Center">
                        <StackPanel Orientation="Horizontal">
                                <TextBox Name="username">aarroyoc</TextBox>
                                <Button Name="refresh" Content="Actualizar"></Button>
                        </StackPanel>
                        <TextBlock Name="status"></TextBlock>
                </StackPanel>
                <ListBox Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionMode="Single" Name="repos"></ListBox>
        </Grid>
</Window>

Adicionalmente, he usado una clase extra para serializar el JSON.

using System.Runtime.Serialization;

namespace GitHubRepos{
    [DataContract(Name="repo")]
    public class GitHubRepo{

        [DataMember(Name="name")]
        public string Name;

        [DataMember(Name="language")]
        public string Language;
        [DataMember(Name="stargazers_count")]
        public int Stars;
    }
}

El resultado es bastante satisfactorio:

 

La entrada Interfaces gráficas multiplataforma en C# con .NET Core y Avalonia aparece primero en Blog - Adrianistan.eu.

» Leer más, comentarios, etc...

Adrianistán

Diversión con punteros en Rust: bloques unsafe

octubre 12, 2017 12:10

Hola, soy Adrián Arroyo y bienvenidos a un nuevo episodio de Diversión con Punteros.

Hoy vamos a hablar de un tema apasionante. Los bloques unsafe de Rust así como de los raw pointers. ¿Has programado en C? Si es así, los raw pointers de Rust son exactamente iguales a los punteros de C. Si no sabes lo que es un puntero, te lo explico.

¿Qué es un puntero?

Un puntero es un tipo de variable que en vez de almacenar el dato, almacena la posición en memoria donde se encuentra el dato.

En lenguajes en lo que todo es un objeto (como Python), nunca trabajamos con los datos reales, sino siempre con punteros, pero el lenguaje lo gestiona de forma automática. En lenguajes más cercanos al metal por contra sí que suele dejarse esta opción.

Nuestro puntero es la variable que contiene 0x00ffbea0 y que apunta a la dirección de memoria donde se encuentra el dato

Rust tiene distintos tipos de punteros: Box, Rc, Arc, Vec, … Estos punteros son transparentes al usuario y muchas veces no tenemos que preocuparnos de su funcionamiento. Sin embargo, muchas veces queremos tener un control más fino del ordenador. Esto lo lograremos con los raw pointers. Se trata de punteros con los que podemos operar y desreferenciar.

Crear raw pointers no supone ningún problema, pero acceder al valor al que apuntan en memoria sí. Podría darse el caso de que no existiera valor alguno o hubiese sido modificado. En los punteros normales, el compilador de Rust se encarga de que no ocurra, pero en los raw pointers el compilador no lo puede saber. Es por ello, que para acceder al valor de un raw_pointer necesitas usar bloques de código unsafe, código inseguro en Rust.

Creando un raw pointer

Lo primero que hay que saber es que existen dos tipos de raw pointers en Rust, los mutables y los inmutables.

Los punteros inmutables tienen el tipo *const T y los mutables el tipo *mut T.

fn main(){ 
    let numero = 5; 
    let puntero = &numero as *const i32; 
    println!("Address: 0x{:x}", puntero as usize); 
    println!("Value: {}",numero); 
}

 

En este ejemplo, creamos una variable con valor 5 y le creamos un puntero, que contiene la dirección de memoria donde está el dato. Para representar la dirección de memoria se suele usar la notación hexadecimal. Antes debemos hacer un cast a usize. usize es un tipo en Rust cuyo tamaño depende de la máquina en cuestión (32 bits en máquinas de 32 bits, 64 bits en máquinas de 64 bits), siendo usado para representar direcciones de memoria, puesto que tiene el tamaño exacto para almacenarlas.

Hasta ahora no hemos usado unsafe. Esto es porque no hemos probado a acceder al valor. Para acceder a un valor, o deferrenciar, usamos el operador *.

fn main(){
    let numero = 5;
    let puntero = &numero as *const i32;
    println!("Address: 0x{:x}", puntero as usize);
    println!("Value: {}",numero);
    unsafe{
        println!("Value: {}",*puntero);
    }
}

Ambos prints imprimen 5. Hasta aquí no hemos hecho nada interesante con punteros. Todo esto era más fácil hacerlo sin punteros. Veamos alguna aplicación práctica de los punteros.

Modificar datos sin control

Si te pongo este código, ¿me puedes decir que salida dará?

fn main(){
    let mut numero = 5;
    let puntero = &mut numero as *mut i32;
    println!("Address: 0x{:x}", puntero as usize);
    unsafe{
        scary_things(puntero);
    }
    println!("Value: {}",numero);
}

Uno podría pensar que como en ningún sitio reasignamos numero, y numero es una variable de tipo i32, que implementa Copy, es imposible modificarle el valor. Y eso es correcto en las reglas de Rust normales, pero en unsafe, podemos pasar el puntero hacia otras funciones (los punteros también son Copy, ocupan el tamaño de un usize). Y esas funciones pueden modificar los datos en memoria a su antojo. Así, pues, la respuesta correcta es indeterminado. Hacer esto es una mala práctica, pero en ocasiones se puede ganar rendimiento o interactuar con una librería de C usando estos métodos.

unsafe fn scary_things(p: *mut i32) {
    *p = 12;
}

fn main(){
    let mut numero = 5;
    let puntero = &mut numero as *mut i32;
    println!("Address: 0x{:x}", puntero as usize);
    unsafe{
        scary_things(puntero);
    }
    println!("Value: {}",numero);
}

Esta sería la versión completa del programa.

Aritmética de punteros

Una vez tenemos acceso a memoria podemos acceder a cualquier parte de memoria (en sistemas operativos modernos, memoria que esté asignada a nuestro programa). En C simplemente podíamos operar con el puntero como si fuese un número, con sumas, restas, multiplicaciones y divisiones. Estas operaciones eran un poco traicioneras porque eran relativas a la máquina. Sumar 1 a un puntero de int equivalía en realidad a sumar 4 al puntero en una máquina de 32 bits. En Rust esto no se permite, pero a cambio tenemos métodos que nos permiten hacer lo mismo. El más importante es offset. El offset nos permite desplazarnos por la memoria hacia delante y hacia atrás.

fn main(){
    let mut numero = 5;
    let b = 35;
    let c = 42;
    let puntero = &mut numero as *mut i32;
    println!("Address: 0x{:x}", puntero as usize);
    unsafe{
        *puntero.offset(1) = 120;
    }
    println!("Value: {}",numero);
    println!("Value: {}",b);
    println!("Value: {}",c);
}

Este programa parte de una suposición para funcionar. Y es que numero, b y c están contiguos en memoria y en el mismo orden que como los que he declarado. En el puntero tenemos la dirección a numero, es decir, a 5. Sin embargo, si avanzamos en la memoria una posición llegaremos a al 35, y si avanzamos dos, llegamos a 42. Entonces podemos editar el contenido de esa memoria. Al acabar el programa b vale 120. Hemos modificado el valor y ni siquiera b se había declarado como mut. Esto os recuerdo, usadlo solo en casos excepcionales.

Reservar memoria al estilo C

Estas cosas empiezan a tener utilidad en cuanto podemos usar memoria dinámica al estilo C, es decir, con malloc, free, calloc y compañía. El equivalente a malloc en Rust suele ser Box o Vec y es lo que debemos usar. Box sabe que espacio en memoria tiene que reservar de antemano y Vec ya está preparado para ir creciendo de forma segura.

extern crate libc;

use libc::{malloc,free};
use std::mem::size_of;

fn main(){
    unsafe {
        let puntero = malloc(10*size_of::<i32>()) as *mut i32;
        for i in 0..10 {
            *puntero.offset(i) = 42;
        }
        for i in 0..10{
            println!("{}",*puntero.offset(i));
        }
        free(puntero as *mut libc::c_void);
    }
}

En este caso usamos malloc como en C para generar un array de forma dinámica con espacio suficiente para almacenar 10 elementos de tamaño i32.

Con esto ya hemos visto el lado oscuro de Rust, la parte unsafe. No hemos visto como llamar a funciones de C directamente, algo que también require usar bloques unsafe.

Como vemos, Rust no nos limita a la hora de hacer cualquier cosa que queramos, solo que nos reduce a los bloques unsafe, para que nosotros mismos tengamos mejor control de lo que hagamos.

La entrada Diversión con punteros en Rust: bloques unsafe aparece primero en Blog - Adrianistan.eu.

» Leer más, comentarios, etc...

Blog Bitix

Obtener el mínimo o máximo de dos, una lista o stream de valores en Java

octubre 12, 2017 09:00

Java

Si tenemos dos valores y queremos obtener el menor con una línea de código, podemos obtenerlo con un a sentencia if o con el operador condicional ? :. Si queremos obtener el menor de tres valores con sentencias if o el operador ? : el problema aparentemente sencillo se complica y si optamos por usar una lista de valores tratándola como si el tamaño fuese desconocido quizá usemos un bucle for junto con una variable que mantenga el menor valor encontrado hasta el momento usando un if que compare el valor menor encontrado con el valor actual de la lista.

Sin embargo, en la API de Java hay dos métodos que permiten simplificar esta tarea, para dos valores podemos usar el método Math.min() y para una lista de valores de tamaño desconocido podemos usar el método Collections.min(). Usando un Stream aún es más sencillo ya que estos poseen un método Stream.min() y Stream.max() que devuelven el valor mínimo y máximo.

Este sería el código para los casos de comparar dos elementos, una lista y un stream de valores. Se que se puede probar con la herramienta JShell incluída entre otras novedades de Java 9. En la lista de 20 valores aleatorios el menor es el 2 y entre la variable a y b el valor mínimo es 19.

Diferentes formas de obtener el valor mínimo y máximo

Para obtener el máximo sería similar pero usando el método Math.max(), Collections.max() o Stream.max().

» Leer más, comentarios, etc...

Poesía Binaria

Rescatando capturas de pantalla perdidos en el disco duro

octubre 12, 2017 08:10

Hace unos días, en el blog Salmorejo Geek, se recordaban unas capturas de pantalla, de 2011, hace muchas distribuciones. Así que, yo me he puesto a buscar algunas viejas capturas de pantalla, lo más viejas posible. Porque, salvo algunos desastres informáticos en mi vida, no he perdido muchos datos, sobre todo en mi vida linuxera. Lo malo es que en los discos tengo muy mala organización. Así que el gran problema es encontrar las capturas.

No he sido de cambiar demasiado de distribución, aunque ha habido varias las que han pasado por mi ordenador.

Firefox, 2004

Visualizando una antigua web que tenía online. Con muchos contenidos. Mucho antes del responsive, y cuando había que tener cuidado de no meter demasiado Javascript en las páginas web, porque por un lado, los motores de Javascript no estaban muy desarrollados y podían llegar a ser lentos, y por otro lado, no en todos lados funcionaba todo, Desde una de las primeras versiones de Firefox, peleando con Internet Explorer todo el día y con muchas ganas.

Gentoo y Fluxbox, Abril de 2008

Desde hace mucho tiempo he tenido programas de KDE en mi ordenador. Me han encantado y me han parecido muy útiles. Aquí estoy configurando una lista de lanzadores, añadiendo los programas más comunes.

Gentoo y Fluxbox, 11 de Julio de 2009

Una época en la que utilizaba Gentoo, una distribución que me duró mucho tiempo. Con la que disfruté mucho y aprendí mucho también. Utilizaba la agenda de Kontact de KDE3 y Konqueror, al mismo tiempo de Xterm, y unas cuantas dockapps laterales.

Gentoo y Fluxbox, 1 de Agosto de 2010

Pŕacticamente utilicé Gentoo y Fluxbox hasta 2012. Compilando todos mis programas en un viejo portátil de 2006, un Pentium M de 32bit, que todavía creo que se puede encender, aunque con problemas en la placa base. Ya andaba yo con este blog, escribiendo en mis ratos libres todo lo que se me iba ocurriendo. En este caso, poner un terminal transparente en el fondo de pantalla. Una cosa que estaba muy de moda en aquellos años.

Gentoo, Fluxbox y Konqueror, 22 de Diciembre de 2010

LMDE XFCE, 17 de Septiembre de 2011

Experimentando con Linux Mint Debian Edition. En un pequeño ordenador.

XFCE, 28 de Agosto de 2012

Como siempre, probando cosass, con thunderbird detrás y un tema de iconos cuyo nombre no recuerdo y ya no uso. Tengo detrás un montón de RSS de blogs. Aunque ya no estoy suscrito a tantos, todavía lo uso como lector de RSS.
El entorno es XFCE.

Hay muchas formas de gestionar esos terminales, para no tener tantos abiertos (a algunos les pierdo la pista durante días), pero bueno, como en todo, siempre hay dos tipos de personas.

Linux Mint XFCE, 3 de Noviembre de 2012

En este caso estaba preparando los posts sobre traducciones con Poedit. En aquella época, para las notas utilizaba Keepnote y para Twitter usaba Hotot.

Linux Mint KDE con KDE4, 20 de Marzo de 2013

Ya con KDE, creo que aún se llamaba KDE, o era KDESC. Cuando ya era estable y se podía utilizar. Porque la transición de KDE3 a KDE4 fue horrorosa. Ya con mi configuración de dos monitores. Como se puede ver el monitor de la izquierda es el del portátil, a 1366×768 y, estaba buscando un móvil con una pantalla mientras naufragaba en foros en la otra.

Blender, 2 de Junio de 2016

Utilizando Blender como editor de vídeo… ya daré guerra con esto. 🙂

Venga, una con Windows, ¡en 2003!

Hace muchos años, montando un vídeo, como se puede ver, de una comunión. El vídeo lo estaba montando con Ulead Videostudio Pro, programa que venia gratis con una tarjeta capturadora, así que, al menos, era legal. Sí, en aquella época retocaba los ojos de las fotos, a veces los ponía totalmente negros, para ver la reacción de quien miraba la foto 🙂

¿Te animas?

¿Tienes alguna captura de pantalla más o menos antigua? ¿Te animas a compartirla? Me la puedes mandar por Twitter, Facebook, Instagram, o dejar un enlace en un comentario.

The post Rescatando capturas de pantalla perdidos en el disco duro appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

El software inflado

octubre 12, 2017 06:24

La Ley de Wirth, enunciada por Niklaus Wirth en 1995, el artífice del lenguaje Pascal y sus derivados, dice lo siguiente: “El software se ralentiza más deprisa de lo que se acelera el hardware” Es un hecho evidente y notorio para todos. La mayoría de software que tenemos instalado funciona más lento que sus versiones […]

Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

La entrada El software inflado aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

» Leer más, comentarios, etc...

Adrianistán

Cheatsheet de contenedores de Rust

octubre 10, 2017 12:52

Raph Levien de Google ha publicado la siguiente infografía o cheatsheet donde podemos ver como funciona cada contenedor de datos en Rust. Es un recurso muy interesante bajo licencia Creative Commons 4.0 BY, que si bien no es necesario para poder programar en Rust, puede sernos de utilidad cuando trabajemos a bajo nivel con el lenguaje.

Enlace original

La entrada Cheatsheet de contenedores de Rust aparece primero en Blog - Adrianistan.eu.

» Leer más, comentarios, etc...

Variable not found

Cancelación de peticiones en ASP.NET Core y MVC

octubre 10, 2017 08:45

ASP.NET CoreImaginad una aplicación ASP.NET Core MVC en la que insertamos un enlace o botón que dirige el navegador hacia la siguiente acción, que realiza una operación compleja y retorna un resultado:
public async Task<IActionResult> GetTheAnswerToLifeUniverseAndEverything()
{
await Task.Delay(30000); // Simulando un proceso costoso...
return Content("42!");
}
Cuando nuestros usuarios pulsen dicho botón, necesariamente habrán de esperar varios segundos para obtener una respuesta. Pero como suelen ser seres poseídos por una entidad demoníaca impacientes, lo normal es que se lancen en un feroz ataque contra el sistema, refrescando la página o pulsando repetidamente el botón de envío como si no hubiera un mañana. Todos hemos escuchado y sufrido en nuestras carnes una agresión de este tipo: “espera, esto parece que no va: click-click-click-click-click-click-click…

Obviamente, esto no hace sino empeorar las cosas. El servidor, que ya estaba ocupado intentando responder la primera petición, no tiene ya que atender a una, sino a todas las que se han generado tras este ataque, cuando en realidad no tiene sentido: para tranquilizar al usuario basta con entregarle el resultado de una de ellas, por lo que todos los hilos sobrantes simplemente están malgastando recursos del servidor realizando operaciones para obtener resultados que nadie va a consumir.

Estaría bien poder cancelar esas peticiones largas si tenemos la seguridad de que ningún cliente está esperándolas, ¿verdad?

Tokens de cancelación de peticiones en ASP.NET Core

En ASP.NET Core, todas las peticiones llevan asociadas un cancellation token, que es utilizado por el framework para indicar que el cliente que la originó ha sido desconectado. Esto ocurre, entre otros motivos, si el usuario pulsa el botón stop, escape, o cierra la pestaña o el navegador mientras está una petición en curso.
Ojo: si estás tras IIS, es él quien realiza la petición a Kestrel, por lo que la cancelación no llegará, al menos hasta que se solucione esta issue del módulo ASP.NET Core de Github.
Ese token de cancelación lo tenemos disponible en todas las peticiones, a través de la propiedad RequestAborted de la clase HttpContext, por lo que podemos consultarlo a nuestra voluntad para ver si la petición sigue estando activa o para cancelar tareas que ya no nos interesen. Por ejemplo, en el siguiente código vemos cómo un middleware que realiza una tarea completa (simulada mediante un delay) puede ser cancelado cuando la petición se cierre:
app.Use(async (ctx, next) =>
{
try
{
// Simulamos una tarea costosa...
await Task.Delay(5000, ctx.RequestAborted);
await next();
}
catch
{
logger.LogInformation("Request aborted!");
}
});
Observad que cuando el token es cancelado porque la conexión con el cliente se ha perdido, la instrucción await Task.Delay() finaliza lanzando una excepción. Ese es el motivo de rodear la acción con un bloque try/catch.
A continuación se muestra el log por consola de una aplicación en la que hemos insertado el middleware anterior. Como se puede ver, primero hacemos una petición que dura 5 segundos, y luego pulsamos repetidas veces F5 de forma que lanzamos un ataque de peticiones, pero éstas van siendo canceladas sucesivamente hasta que la última finaliza con éxito:
Hosting environment: Development
Content root path: C:\Users\josem\source\repos\MyFirstAspNetCoreApplication\MyFirstAspNetCoreApplication
Now listening on: http://localhost:58842
Application started. Press Ctrl+C to shut down.

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:58842/
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 5015.3097ms 404

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:58842/
info: MyFirstAspNetCoreApplication.Startup[0]
Request aborted!
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 1406.2337ms 0

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:58842/
info: MyFirstAspNetCoreApplication.Startup[0]
Request aborted!
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 620.9865ms 0

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:58842/
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 5011.2962ms 404
Este token de cancelación podemos utilizarlo normalmente en llamadas de tinte asíncrono, como consultas a bases de datos o llamadas a un API externo:
var invoice = await context.Invoices.FirstOrDefaultAsync(
invoice => invoice.Id == id,
ctx.RequestAborted
);
Y, por supuesto, en cualquier otro tipo de código como bucles, bifurcaciones, etc:
app.Use(async (ctx, next) =>
{
for (int i = 0; i < 10 && !ctx.RequestAborted.IsCancellationRequested; i++)
{
logger.LogInformation($"Loop {i}/5");
await Task.Delay(1000);
}
});

¿Y en ASP.NET Core MVC?

ASP.NET Core MVCPues en el framework MVC es igual de sencillo, o incluso más :)

En primer lugar, el token de cancelación que en ASP.NET Core puro veíamos en HttpContext.RequestAborted sigue estando ahí. Es decir, nada impide que accedamos al contexto y lo utilicemos a nuestro antojo:
public Task<IActionResult> Get(int id)
{
var invoice = await _invoiceServices
.GetByIdAsync(id, HttpContext.RequestAborted);
if (invoice == null)
{
return NotFound();
}
return Ok(invoice);
}
Sin embargo, aún se puede mejorar un poco. Si una acción es cancelable, basta con añadirle un parámetro de tipo CancellationToken, y el framework automáticamente le inyectará el valor de HttpContext.RequestAborted:
public Task<IActionResult> Get(int id, CancellationToken cancellationToken)
{
var invoice = await _invoiceServices.GetByIdAsync(id, cancellationToken);
if (invoice == null)
{
return NotFound();
}
return Ok(invoice);
}
De esta forma aumentaremos la legibilidad del código, puesto que será más visible el hecho de que la acción sea cancelable, y al mismo tiempo facilitaremos las pruebas unitarias del método porque podremos inyectarle el token de forma manual y probar comportamientos más fácilmente.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Jesús Perales

Renovación forzada de blog

octubre 09, 2017 03:00

Renovación forzada de blog

Vaya sorpresa me lleve este fin de semana la plataforma que utilizaba para alojar el blog (Openshift 2) cerro para que todos migraran a su versión 3, actualmente no he publicado nuevas entradas en el blog pero si tenia algunos borradores que no pude salvar.

Si enviaron correos y dieron tiempo, pero yo solo los postergaba debido a que el trabajo me consume.

Por el momento decidí utilizar algunos créditos que tenia para Digital Ocean y ahora el blog esta alojado allí, mientras aprendo y veo como volver a alojarlo en Openshift 3.

El servicio de Digital Ocean es muy bueno, pude generar el blog en cuestión de minutos, pero para recuperar el contenido me di a la tarea de utilizar un backup antiguo que tenia por alli en Google Drive y gracias a mi cliente de RSS( Feedly) pude recuperar todo el contenido que no tenia respaldo.

También pase de Ghost 0.11.0 a 1.12.0, tenia que utilizar esa versión antigua ya que era un problema tratar de utilizar la nueva versión, debido al Nodejs que Openshift soporta, me dio una sorpresa encontrar que ahora ya no es posible pegar las urls de mi CDN directamente, es necesario instalar un plugin para tener algo parecido a la funcionalidad que tenia anteriormente, los desarrolladores de ghost comentaban que habían quitado la opción debido a que era un problema mantenerlo.

Con está versión de Ghost también se actualiza el tema por defecto llamado Casper, que no gusta, veré si encuentro uno, ahora es mas sencillo cambiar los temas, antes se tenia que reiniciar por completo el CMS.

Alojar mi blog en Digital Ocean solo durará aproximadamente 5 meses, espero poder echarlo a volar en Openshift, antes de ese tiempo.

» Leer más, comentarios, etc...

Poesía Binaria

Cómo distribuir cálculos entre varios núcleos para acelerar procesos de computación [ejemplos en C++]

octubre 09, 2017 08:30

Distribuir cálculos entre varios hilos de ejecución
Hace años, más o menos a finales del siglo XX y principios del XXI y, como vimos en mi anterior post, hubo una guerra por los Megahercios. Es decir, las grandes compañías de procesadores, en aquella época Intel y AMD que, aunque había más fabricantes, éstos eran los que más sonaban, peleaban para que sus procesadores corrieran más que los de la competencia. Es más, como el usuario de a pie siempre se ha regido por la frase: “burro grande, ande o no ande“, siempre querrá un procesador con más megahercios, una cámara con más megapixels o una televisión con más pulgadas.

Pero ahora vivimos una realidad un poco distinta. Los procesadores actuales, que podemos tener en nuestro ordenador personal o encontramos en un servidor, grosso modo, intentan hacer operaciones más complejas utilizando menos ciclos, realizar varias operaciones a la vez y, también, consumir menos energía. Es más, en 2004 se alcanzaron los 4GHz, y pocos procesadores actuales los superan. Para entendernos mejor, antes sabían sumar, restar, multiplicar y dividir. Y, cuando queríamos hacer una raíz cuadrada teníamos que hacerlo en función de las operaciones que sabíamos hacer. Pero ahora las CPUs saben hacer raíces cuadradas sin necesidad de más indicaciones. Al mismo tiempo, si tenemos que hacer muchas operaciones, podemos agruparlas y procesar varios grupos a la vez. Esta explicación no es muy exacta, pero así nos hacemos una idea de cómo va la cosa.

Hoy vamos a centrarnos en la capacidad de desempeñar varios trabajos a la vez por la CPU. Y eso vendrá determinado por el número de núcleos o de hilos de ejecución que sea capaz de ejecutar dicha CPU. Por supuesto, el buen funcionamiento del sistema vendrá determinado por cómo el sistema operativo reparta las tareas entre los núcleos del procesador. O si, por ejemplo, tenemos una placa base con varias CPU, cómo se repartan las tareas entre éstos.

Varios procesos diferentes

Empecemos por el principio. Una vez que los sistemas operativos han evolucionado, y ya son capaces de manejar los núcleos como churros, podemos ver cómo si ejecutamos varias aplicaciones pesadas en nuestro sistema, éstas se ejecutan utilizando varios núcleos. Mientras que si sólo ejecutamos una, sólo habrá un núcleo trabajando.
Como vemos en las siguientes imágenes, podemos ver cuándo había sólo una aplicación gorda (por así decirlo) corriendo. En este caso sólo está trabajando intensamente la CPU3, las demás, algo también, porque un ordenador ejecuta muchos servicios.
Hay una aplicación pesada corriendo
Pero como vemos en la siguiente imagen, hay dos CPUs trabajando intensamente, la CPU4 y la CPU5. Porque tengo dos programas de cálculo corriendo a la vez (el segundo terminó muy pronto, pero se puede ver el intervalo de trabajo).
Dos programas calculando a la vez

Cuando un proceso necesita músculo

Pero seguro que se nos ocurren tareas en las que necesitamos mucha más potencia de cálculo. Éstas pueden ser, por citar algunos ejemplos: compresión/descompresión de vídeo, simulaciones, procesamiento 3D, tratamiento de imagen, criptografía, etc. Son procesos que, pueden llevar mucho tiempo y, aunque podremos lanzar varios procesos iguales a la vez y comprimir 4 vídeos en el tiempo que se tarda en comprimir uno solo. A nosotros nos interesaría poder comprimir uno, pero en una cuarta parte del tiempo…
Nueve mujeres no pueden tener un niño en un mes
Es cierto que la compresión de vídeo es un terreno que ya está muy dominado en este aspecto. Existe tanto software como hardware especializado en realizar esta tarea de manera muy eficiente y muy rápida (como hablaré en futuros posts, podemos, por ejemplo, utilizar ffmpeg o avconv para comprimir vídeo utilizando varios núcleos y acelerar dicho proceso). Pero siempre está bien poder hacer lo mismo por nosotros mismos a la hora de crear un programa que realice un cálculo complejo, o un efecto, o análisis de imagen. De la misma manera de antes, ya que tenemos varios núcleos en nuestro ordenador, nos interesa terminar pronto en lugar de hacer varias imágenes a la vez. Incluso en un programa de edición de vídeo, nos interesa tener una previsualización en tiempo real y, a veces, en la máxima resolución posible.

Algunas cosas a tener en cuenta

Eso sí, para aprovechar el multiprocesamiento, y hacer que varios núcleos puedan coordinarse para procesar el trabajo que queremos y sacarlo antes, primero necesitamos optimizar nuestros algoritmos para ello y tener en cuenta algunas cosas:

  • Tenemos que independizar los cálculos lo más posible. Es decir, si un cálculo depende del resultado de la iteración anterior, no vamos a poder paralelizar el cálculo total. Aunque, por ejemplo, si queremos aplicar un filtro a una imagen (si no anula lo que he dicho en la frase anterior), sí que podemos dividirla en trozos y procesar cada trozo por separado. O, si es un sumatorio, y cada elemento se puede calcular de forma independiente, podemos calcular y sumar resultados parciales y, cuando acabemos, sumar los resultados parciales… ya lo iremos viendo.
  • Cuidado con la memoria. Es aconsejable utilizar, en la medida de lo posible, zonas de memoria diferentes en cada uno de los hilos de ejecución que lancemos para evitar condiciones de carrera, o el tener que introducir mutex o bloqueos a la hora de acceder a un dato.
  • ¡Cuidado con los bloqueos! Un poco realacionado con el punto anterior. Si necesitamos hacer que un hilo espere a otro, puede que tengamos potencia desaprovechada y tardemos mucho. Hay veces que es inevitable, pero en muchos casos no.
  • Monitorizar el tiempo. Si una tarea, programada para un sólo hilo, imaginemos que tarda 10 segundos. Cuando la programamos de forma multihilo y lanzamos dos hilos, es muy difícil que tarde la mitad, 5 segundos. Tal vez, tardará 6 segundos, y si lanzamos 3 hilos, se aproximará a 5 segundos. Esto está bien, ya que seguramente tengamos algún acceso más a variables, tendremos que acceder a algún dato de otra forma, o incluso hacer operaciones antes y después del procesamiento en grupo. Eso sí, si procesando con dos hilos tardamos 9 segundos, o más de 10 pueden pasar dos cosas: o la tarea no es viable en multiproceso, o estamos haciendo algo mal.
  • ¿Es viable el multiproceso? Hay tareas que tardan tan poco que no merece la pena ponerse a paralelizarlas. O, al menos, complicarnos la vida para ello. Es decir, muchas veces, para paralelizar una tarea, tenemos que preparar los datos datos, realizar el procesamiento y reorganizar los datos de nuevo. Así que el hecho de preparar los datos y reorganizarlos, no debe invertir mucho tiempo. Como ejemplos de esto, podemos pensar, en calcular un sumatorio, debemos reservar memoria para hacer sumas parciales y definir qué parte calculará cada hilo. Cuando termine el procesamiento debemos sumar todas las sumas parciales. Vemos que el proceso se complica un poco, así que, en operaciones muy sencillas y rápidas, tal vez la preparación y reorganización tarden mucho en comparación con el cálculo y estaremos perdiendo tiempo.

Hilos y núcleos

Para realizar el reparto de tareas, vamos a crear varios hilos de ejecución en nuestro programa. El sistema operativo se va a encargar de repartir estos hilos entre los núcleos o cores de nuestra CPU (o los hilos que sea capaz de ejecutar, porque muchas CPUs pueden correr dos hilos por cada core. A lo mejor no son dos hilos simultáneos simultáneos, pero casi). Aunque, por ejemplo si nuestro ordenador puede ejecutar 4 hilos a la vez, como poder, podemos lanzar 10 o 12 hilos. En este caso, esos 12 hilos se van repartiendo el tiempo en los 4 núcleos y, si la tarea a realizar es muy pesada, puede que el multiprocesamiento nos haga perder tiempo.

De toda la vida de la informática, o gran parte de ella, hemos tenido un núcleo y ese núcleo ha tenido que ejecutar cientos de tareas pequeñas. El núcleo se las ha repartido para que parezca que se ejecutan a la vez. Si tenemos 4 núcleos, los 4 se repartirán las tareas.

Repartir tareas entre hilos

Existen muchas técnicas para el multiprocesamiento. Por ejemplo, cuando tenemos un servidor (web, base de datos, ftp, etc), normalmente se crearán varios hilos dependiendo de las peticiones que vayan entrando. O puede que necesitemos varios hilos corriendo constantemente generando y recibiendo datos a la vez.

Aunque en este post me voy a centrar en bucles, tareas que podemos hacer de forma iterativa, de las que conocemos su inicio y su final. Y por eso los ejemplos sobre compresión y cálculos. Procesos del tipo: tengo que dar 100 iteraciones, del 0 al 99 y tengo 4 núcleos:

  • núcleo0, tú darás darás las 25 primeras, del 0 al 24 (for i=0;i
  • núcleo1, las siguientes 25, del 25, al 49 (for i=25;i
  • núcleo2, las siguientes, del 50 al 74 (for i=50;i
  • núcleo3, las restantes, del 75 al 99 (for i=75;i

Por lo que ahora, el inicio y el fin del bucle tendremos que calcularlo en función del número de hilos que vayamos a lanzar, no es difícil, ni pesado, pero ya estamos haciendo cosas que antes no hacíamos.

Si, por ejemplo tenemos un bucle for que se inicia en 0 y finaliza en n, cada hilo tendrá su propio bucle for desde:
inicio del bucle
y terminará en:

Por supuesto, dadas estas fórmulas, cada hilo sabe qué número de hilo es y cuántos vecinos tiene en ese procesamiento. Y así, sustituyendo, n=100, totalHilos=4 e hilo, dependiendo del hilo actual por 0,1,2,3 nos saldrán los números de antes. Ya de manera general, si tenemos un inicio que no es 0, llamémosle xi y un fin xf, podemos decir, de manera general que:

Donde siempre podemos coger, sacar (xf-xi), llamarlo n y nos queda algo muy parecido a lo de antes.

Uso de bibliotecas

¡Cómo no! Hay herramientas que permiten hacer esto en muchos lenguajes, por ejemplo, ya que estamos con C++, tenemos OpenMP capaz de hacer muchas cosas. Pero hoy vamos a centrarnos un poco en teoría, matemáticas y cómo funciona todo esto.

¡Ejemplos, ejemplos!

Cálculo de PI

Para los ejemplos voy a utilizar C++11, más que nada porque no tenemos que liar mucho jaleo para hacer threads. Empecemos calculando el número Pi con la fórmula:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <chrono>
#include <iostream>
#include <thread>
#include <list>
#include <iomanip>

using namespace std;

int Nthreads;
double* sump; //Array donde se guardarán las sumas parciales

void calcula_pi (int hilo, long unsigned n, double h)
{

    double sp=0.0; // La suma parcial la hacemos en un espacio de memoria cercano, que será más rápido

    for (long unsigned i=(n*hilo/Nthreads); i <(n*(hilo+1))/Nthreads; i++)
    {
        // Cálculamos Sum[i=0, i=n] 4 / (1 + (1/n * (i-0.5)^2) siendo h=1/n mediante sumas parciales en
        // cada hilo

        double x = h * ((double)i - 0.5);
     
        sp+= 4.0 / (1.0 + x*x);
    }
    // Por último rellenamos el espacio correspondiente del array de
    // sumas parciales.
    sump[hilo]+=sp;
}

int main(int argc, char *argv[])
{
    double mypi, sum=0;
    double h ;
    long unsigned n;

    // Parseamos argumentos. El primer argumento será n y el segundo el número de hilos
    // Por defecto, n=100 y el número de hilos=1
    n= 100;
    Nthreads = 1;
    if (argc > 2)
        {
            n = atoi(argv[2]);
            Nthreads = atoi(argv[1]);
        }
    else if (argc > 1)
        {
            Nthreads = atoi(argv[1]);
        }
   
    auto time_inicio=chrono::high_resolution_clock::now();

    // Preparación del entorno: reserva de memoria.
    sump=new double [Nthreads];
 
    list<thread> threads;  


    // Procesamiento del dato
   
    // Evitamos que todos los threads calculen un dato común.
    // h = 1/n, además, lo sacamos del sumatorio.
    h = 1.0 / (double) n;

    // Cálculo de PI con 1/n * Sum[i=0, i=n] 4 / (1 + (1/n * (i-0.5)^2)
   
    for (int i=0; i<Nthreads; i++)
    {
        // Lanzamos threads que ejecutan la función calcula_pi, les pasamos su número
        // de thread, n y h
        threads.push_back(thread(calcula_pi,i,n, h));
    }

    // Cada thread lo unimos al proceso principal, por lo que esperamos que terminen
    // todos los threads lanzados.
    for (auto &t: threads)
    {
        t.join();
    }

    // Fin del procesamiento y ahora iniciamos la reorganización, en este caso
    // sumamos las sumas parciales en una sola variable y liberamos memoria
    for (int i=0;i<Nthreads;i++)
    {
        sum=sum+sump[i];
    }
    mypi = h * sum;
       
    delete [] sump;

    auto time_fin=chrono::high_resolution_clock::now();
    cout << "Tiempo invertido: " << chrono::duration<double,milli>(time_fin-time_inicio).count() << " ms, con: "<<Nthreads<< " hilos." << endl;
    cout << "PI= "<<setprecision(9)<<mypi<<endl;
    return 0;
}

Este ejemplo tal vez tendría más sentido si utilizáramos precisión arbitraria, aunque como ejercicio no viene mal.
Para compilar este ejemplo debemos hacer:

g++ -o pi pi.cpp -std=c++11 -lpthread

Modificando una imagen jpeg

Para terminar, vamos a modificar el brillo y contraste de una imagen de una imagen por componentes. Pudiendo aumentar brillo y contraste de rojos, verdes o azules por separado. Utilizaré el ejemplo del post del enlace, aunque añadiré algunas modificaciones para hacerlo multihilo.

He de decir que no he optimizado mucho el código, por lo que, seguro que trabajando un poco más en él se pueden conseguir mejores resultados. Además, hay que tener en cuenta que la lectura y escritura del fichero de imagen no es multiproceso por lo que esto es un tiempo fijo que nuestro programa tiene que invertir.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#include <stdio.h>
#include <jpeglib.h>
#include <stdlib.h>
#include <math.h>
#include <chrono>
#include <iostream>
#include <thread>
#include <list>

using namespace std;
typedef struct TImage
{
  int width;
  int height;
  int bytes_per_pixel;
  int color_space;
  int size;
  unsigned char *data;
} TImage;

int read_jpeg_file( char *filename, TImage *img )
{
    /* these are standard libjpeg structures for reading(decompression) */
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    /* libjpeg data structure for storing one row, that is, scanline of an image */
    JSAMPROW row_pointer[1];
    FILE *infile = fopen( filename, "rb" );
    unsigned long location = 0;
    int i = 0;
   
    if ( !infile )
    {
      printf("Error opening jpeg file %s\n!", filename );
      return -1;
    }
    /* here we set up the standard libjpeg error handler */
    cinfo.err = jpeg_std_error( &jerr );
    /* setup decompression process and source, then read JPEG header */
    jpeg_create_decompress( &cinfo );
    /* this makes the library read from infile */
    jpeg_stdio_src( &cinfo, infile );
    /* reading the image header which contains image information */
    jpeg_read_header( &cinfo, TRUE );
    /* Uncomment the following to output image information, if needed. */
    img->width=cinfo.image_width;
    img->height=cinfo.image_height;
    img->bytes_per_pixel=cinfo.num_components;
    img->color_space=cinfo.jpeg_color_space;

    /* Start decompression jpeg here */
    jpeg_start_decompress( &cinfo );

    /* allocate memory to hold the uncompressed image */
    img->size=cinfo.output_width*cinfo.output_height*cinfo.num_components;
    img->data = (unsigned char*)malloc( img->size );
    /* now actually read the jpeg into the raw buffer */
    row_pointer[0] = (unsigned char *)malloc( cinfo.output_width*cinfo.num_components );
    /* read one scan line at a time */
    while( cinfo.output_scanline < cinfo.image_height )
    {
      jpeg_read_scanlines( &cinfo, row_pointer, 1 );
      for( i=0; i<cinfo.image_width*cinfo.num_components;i++)
        {
          img->data[location++] = row_pointer[0][i];
        }
    }
    /* wrap up decompression, destroy objects, free pointers and close open files */
    jpeg_finish_decompress( &cinfo );
    jpeg_destroy_decompress( &cinfo );
    free( row_pointer[0] );
    fclose( infile );
    /* yup, we succeeded! */
    return 1;
}

/**
 * write_jpeg_file Writes the raw image data stored in the raw_image buffer
 * to a jpeg image with default compression and smoothing options in the file
 * specified by *filename.
 *
 * \returns positive integer if successful, -1 otherwise
 * \param *filename char string specifying the file name to save to
 *
 */

int write_jpeg_file( const char *filename, TImage *img )
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
   
    /* this is a pointer to one row of image data */
    JSAMPROW row_pointer[1];
    FILE *outfile = fopen( filename, "wb" );
   
    if ( !outfile )
    {
            printf("Error opening output jpeg file %s\n!", filename );
            return -1;
    }
    cinfo.err = jpeg_std_error( &jerr );
    jpeg_create_compress(&cinfo);
    jpeg_stdio_dest(&cinfo, outfile);

    /* Setting the parameters of the output file here */
    cinfo.image_width = img->width;
    cinfo.image_height = img->height;
    cinfo.input_components = img->bytes_per_pixel;
    cinfo.in_color_space = JCS_RGB; //img->color_space;
    /* default compression parameters, we shouldn't be worried about these */
    jpeg_set_defaults( &cinfo );
    /* Now do the compression .. */
    jpeg_start_compress( &cinfo, TRUE );
    /* like reading a file, this time write one row at a time */
    while( cinfo.next_scanline < cinfo.image_height )
    {
            row_pointer[0] = &img->data[ cinfo.next_scanline * cinfo.image_width *  cinfo.input_components];
            jpeg_write_scanlines( &cinfo, row_pointer, 1 );
    }
    /* similar to read file, clean up after we're done compressing */
    jpeg_finish_compress( &cinfo );
    jpeg_destroy_compress( &cinfo );
    fclose( outfile );
    /* success code is 1! */
    return 1;
}

float _byc(int hilo, int Nthreads, TImage *img, double bred, double bgreen, double bblue, double cred, double cgreen, double cblue)
{
  int i;
  double tanred, tangreen, tanblue;
    unsigned char vred, vgreen, vblue;
  int v;
  tanred=tan ((cred + 1.0) * 0.78539816);
  tangreen=tan ((cgreen + 1.0) * 0.78539816);
  tanblue=tan ((cblue + 1.0) * 0.78539816);
    for (i=img->size*hilo/Nthreads; i<img->size*(hilo+1)/Nthreads; i+=3)
    {
            vred = img->data[i];
            vgreen = img->data[i+1];
            vblue = img->data[i+2];
           
      if (bred<0)
        vred=round(vred*(1+bred));
      else
        vred+=round((255-vred)*bred);

      if (bgreen<0)
        vgreen=round(vgreen*(1+bgreen));
      else
        vgreen+=round((255-vgreen)*bgreen);

      if (bblue<0)
        vblue=round(vblue*(1+bblue));
      else
        vblue+=round((255-vblue)*bblue);

      v=round( (vred - 128) * tanred) + 128;
      if (v>0 && v<255)
                vred=v;
      else if (v<0)
                vred=0;
      else
                vred=255;

      v=round( (vgreen - 128) * tangreen) + 128;
      if (v>0 && v<255)
                vgreen=v;
      else if (v<0)
                vgreen=0;
      else
                vgreen=255;

      v=round( (vblue - 128) * tanblue) + 128;
      if (v>0 && v<255)
                vblue=v;
      else if (v<0)
                vblue=0;
      else
                vblue=255;

            img->data[i] = vred;
            img->data[i+1] = vgreen;
            img->data[i+2] = vblue;
    }
}

float byc(int Nthreads, TImage *img, double bred, double bgreen, double bblue, double cred, double cgreen, double cblue)
{
    list<thread> threads;
    for (int i=0; i<Nthreads; i++)
        {
            threads.push_back(thread(_byc,i,Nthreads, img, bred, bgreen, bblue, cred, cgreen, cblue));
        }

    for (auto &t: threads)
        {
            t.join();
        }

}

int main(int argc, char*argv[])
{
    char infilename[] = "street-601320.jpg";
    TImage img;
    int Nthreads =1;

    if (argc >= 2)
        {
            Nthreads = atoi(argv[1]);
        }

    /* Try opening a jpeg*/
    if( read_jpeg_file( infilename, &img ) > 0 )
        {
            auto time_inicio=chrono::high_resolution_clock::now(); 
            byc(Nthreads,&img, 0.1, 0.1, 0.2,0.1, -0.1, 0.8);
            auto time_fin=chrono::high_resolution_clock::now();
            cout << "Tiempo invertido: " << chrono::duration<double,milli>(time_fin-time_inicio).count() << " ms, con: "<<Nthreads<< " hilos." << endl;
            write_jpeg_file("foto_brillo.jpg", &img);
        }
    return 0;
}

Para compilar debemos hacer:

g++ -o brillo_threads brillo_threads.cpp  -std=c++11 -lpthread -ljpeg

The post Cómo distribuir cálculos entre varios núcleos para acelerar procesos de computación [ejemplos en C++] appeared first on Poesía Binaria.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 295

octubre 09, 2017 06:55

Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Performance tests

Conceptos / Patrones / Buenas prácticas

Data

HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Cuaderno de software

Back to code, capítulo 2: cargar configuración en Spring boot

octubre 09, 2017 04:23

Aviso al lector: Estoy aprendiendo así que puede que en esta serie de posts meta la pata, no tomes esto como un tutorial son mis notas de como lo aprendí. Es mas, si puedes corregirme te lo agradeceré infinitamente.

Mis dudas a resolver en esta tarea:
1º ¿Cómo cargar parámetros en Spring boot desde un fichero?
2º ¿Cómo cargar parámetros en Spring boot desde linea de comandos?
3º ¿Cual es la prioridad?

Documentación de Spring boot sobre configuración.
Ejemplo en el que me he inspirado

¿Donde dejar la configuración?
Por ahora lo dejaré en src/main/resources/application.properties que Spring boot parece que se lo lee el solito (pendiente confirmar si java también lo hace porque me suena que cualquier properties en el classpath vale). Podrías no llamarlo application.properties pero tendrías que especificar el nombre o el path.

1º ¿Cómo acceder a las variables de configuración en el fichero application.properties?
La manera que me ha parecido mas “rápida” de implementar es este código en aquellas clases en las que vayamos a acceder a alguno de los parámetros de configuración:
@Value("${name:DefaultValue}")
private String name;

La aproximación de utilizar este código para acceder a las variables de configuración puede tener sentido si tenemos pocos parámetros (bbdd,user,pass por ejemplo) pero me da que con unas decenas de lineas de configuración tiene que se un jolgorio majo majo.

La manera que tiene pinta de ser mas javera parece esta:
External configuration: Type-safe Configuration Properties. Me ha gustado, aprovechas que cargas el properties para validar que los valores tienen buena pinta y si algún dia cambias el nombre de la variable solo lo modificas en un sitio, buena pinta (aunque no tengo claro siquiera que lo haya hecho del todo bien. Por ahora esta va a ser la aproximación que siga. Aquí una solución con un ejemplo de @Value y uno de @ConfigurationProperties y aquí mi solución definitiva utilizando solo @ConfigurationProperties.

2º ¿Cómo pasar un parámetro de configuración desde linea de comandos?
java -jar target/bogindexer-1.0-SNAPSHOT.jar --greeting=cocotero

3º ¿Cual es el orden de prioridad de carga de las configuraciones?
Aquí está la documentación de Spring boot al respecto.

“Learning path” status:

  • Spring boot base runable
  • Spring boot command line
  • Spring boot load configuration
  • Spring boot logging
  • Get content from URL
  • Parse HTML to extract content of a tag
  • Spring boot persist to SQLite with jooq
  • Abstract persist to inject a driver
  • Parse HTML to extract all ocurrences based on a class name
  • Web scrapping
  • PDF scrapping
  • XLS scrapping
  • Interact with a REST API

» Leer más, comentarios, etc...

Blog Bitix

Ejemplo con el cliente HTTP/2 de Java

octubre 08, 2017 10:00

Java

Otra de las nuevas funcionalidades incluidas en Java 9 aunque en modo incubación es el cliente con soporte para HTTP/2 para realizar peticiones a recursos usando este protocolo más eficiente y rápido. Al mismo tiempo se ha simplificado el código necesario para realizar una petición y obtener el resultado de una URL. También se ha añadido la funcionalidad de realizar peticiones asíncronas y creación de conexiones de WebSockets.

Las clases importantes de esta nueva API con HttpClient, HttpRequest y HttpResponse. Estas clases se encuentran en el módulo de incubación jdk.incubator.httpclient, una vez que sea definitiva la API se renombrará el módulo.

El siguiente ejemplo realiza una petición a la página del buscador Google con unas cabeceras y obtiene el código de estado, las cabeceras devueltas y el cuerpo de la página de resultado. En la declaración del módulo para usar el cliente hay que indicar que tiene como requerimiento su módulo de jdk.incubator.httpclient.

Petición con el cliente de Java para HTTP/2

Otras fomas de manejadores del resultado de la petición son los siguientes:

Se puede definir la política de cómo procesar las redirecciones para seguirlas, no seguirlas o solo si son seguras o utilizan el mismo protocolo.

Con la clase SSLContext es posible establecer autenticación para el cliente usando un certificado como muestro en el artículo Autenticación mutua de cliente y servidor con certificados.

Para ejecutar el ejemplo usando Gradle y Java 9 hay que añadir un poco de configuración al script de construcción que posiblemente en un futuro no será necesaria cuando se mejore el soporte.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew run.

» Leer más, comentarios, etc...

Cuaderno de software

Back to code, capítulo 1: Spring boot para hacer una herramienta de consola

octubre 08, 2017 06:06

Aviso al lector: Estoy aprendiendo así que puede que en esta serie de posts meta la pata, no tomes esto como un tutorial son mis notas de como lo aprendí. Es mas, si puedes corregirme te lo agradeceré infinitamente.

Hace unos meses, antes de dejar aparcada una vez mas mi intención de volver a programar hablé con Luis y con Guille y se lo dejé claro “¿Qué me miro para hacer webs en Java?” su respuesta fué bastante clara:
Spring boot

Pero claro, en esta intentona, la tarea es construir un scrapper que almacene el contenido del BOG en un sistema de persistencia, vamos, que no es una web, pero me dije “a ver si con Spring boot se van a poder hacer cosas que no sean webs” y así es, es mas, tienen unas guías la mar de interesantes de como hacer cosas.

Este es el ejemplo en el que me basé para empezar a construir bogindexer.

https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample-simple

Mi versión (a mi no me hacía falta un servicio, igual si algún día me saco primero de Java necesitaré un servicio para un “Hola mundo”, pero por ahora no), que acaba con un elegante “Hello world” aquí:

https://github.com/penguinjournals/bogindexer-java/tree/back-to-code-capitulo-1

Bola extra de Penguin:
Mi filosofía es que nada está hecho si no es entregable, así que este código ya es “empaquetable y entregable” como un jar. ¿Aporta valor? No, pero me resuelve el vertical completo hasta la entrega a cliente para no encontrarme en la víspera de la entrega con que no soy capaz de compilar un jar ejecutable.

“Learning path” status:

  • Spring boot base runable
  • Spring boot command line
  • Spring boot load configuration
  • Spring boot logging
  • Get content from URL
  • Parse HTML to extract content of a tag
  • Spring boot persist to SQLite with jooq
  • Abstract persist to inject a driver
  • Parse HTML to extract all ocurrences based on a class name
  • Web scrapping
  • PDF scrapping
  • XLS scrapping
  • Interact with a REST API

» Leer más, comentarios, etc...

Cuaderno de software

Back to code, capítulo 0

octubre 08, 2017 05:47

Vuelvo a programar, y voy a intentar ir registrando aquí el camino que sigo, principalmente como notas para mí pero también por si alguien tiene cosas que aportar.
He pasado los últimos 7-8 años en torno a roles mas cercanos a los servicios en producción y a la definición de ciclos de desarrollo pero el código que escribía (chef, ansible, scripts de mantenimiento, scripts de parseo de grandes volúmenes de datos) nunca eran herramientas completas.
Ahora estoy volviendo al ruedo por iniciativa personal, me gustaba mucho construir cosas y me di cuenta de que poner el código de otros en marcha no era lo mío. Pero también vuelvo porque mi trabajo me lo está permitiendo.
Por si esto fuese poco vuelvo con Java, un lenguaje que intenté evitar en la universidad y que nunca he utilizado en el mundo laboral en primera persona (pese a que estos 7-9 años he formado parte de equipos que trabajaban casi exclusivamente en java).
En mi caso me funciona bien el learn by doing así que me he planteado un pequeño proyecto personal con un alcance mas bien pequeño pero unas pautas técnicas a cumplir muy claras. Voy a intentar scrappear el BOG (Boletín oficial de Guipuzkoa) y clasificar el máximo de información posible que hay en el.
Como he comentado para ello voy a utilizar Java y me voy a apoyar en Springboot gestionando las dependencias con Maven. Voy a hacerlo con estas ideas porque:
1º – Viniendo de lenguajes como PHP o Python (interpretados y con “una puta anarquía de tipos” como dice mi compañero) quiero probar y entender todo el tema del tipado. Además es compilado O_o.
2º – Springboot está construido sobre la filosofía de convention over configuration.
3º – Se que gradle es lo que mola y lo peta ahora pero creo que Maven es como Java 1.6, queda Maven “pa’ rato”.

Dicho esto, mi “learning path” pretendo que sea este:

  • Spring boot base runable
  • Spring boot command line
  • Spring boot load configuration
  • Spring boot logging
  • Get content from URL
  • Parse HTML to extract content of a tag
  • Spring boot persist to SQLite with jooq
  • Abstract persist to inject a driver
  • Parse HTML to extract all ocurrences based on a class name
  • Web scrapping
  • PDF scrapping
  • XLS scrapping
  • Interact with a REST API

» Leer más, comentarios, etc...

Jesús Perales

Nginx vs Apache

octubre 07, 2017 07:37

Nginx vs Apache

Hasta hace algunos años siempre que se hablaba de montar un servidor web rondaba el nombre de Apache y por lo general era la opción inicial, ahora las cosas han cambiado y decidí recopilar información de fuentes confiables, traducirla al español, de por que Apache ya no es la mejor opción, al menos no de la antigua forma.

Apache es un servidor desarrollado en 1995, es una plataforma muy estable diseñada para ser extendida mediante módulos.

Los modulos de Apache lo proveen de muchas funcionalidades, entre ellas la posibilidad de servir contenido dinámico y ello dio paso a el stack XAMPP(Apache, MariaDB[Antes se usaba MySQL], PHP, Perl).

Nginx vs Apache

Apache esta diseñado para crear un thread/hilo por cada cliente que necesite ser atendido, este aislamiento ayudo a agilizar el desarrollo y la innovación.

La innovación trajo consigo un gran crecimiento en Internet y los usuarios comenzaron a crecer exponencialmente.

Nginx vs Apache

A su vez los componentes de las paginas comenzaron a ser mas y mas pesados y los usuarios menos pacientes al esperar que una pagina web cargue completamente.

Nginx vs Apache

Los cambios en el volumen de trafico de usuarios y páginas pesadas comenzaron a ser un desafío al modelo de un thread/hilo por usuario que tanto ayudo a Apache en un principio.

Sumado a eso comenzó a extenderse una practica llamada HTTP keepalive que permite disminuir el tiempo de carga de una página haciendo peticiones paralelas de los recursos, cada petición abierta es atendida por un proceso httpd, por esta razón Apache necesita crear numerosos procesos largos.

Nginx vs Apache

Para mejorar la portabilidad y escalabilidad en algunas plataformas, los principales desarrolladores de Apache crearon dos modelos de procesamiento adicionales (denominados módulos multiprocesamiento o MPM).

El MPM reemplazó los procesos separados de httpd con un pequeño número de procesos secundarios que ejecutaban múltiples subprocesos de trabajo y asignaban un subproceso por conexión. Esto fue útil en muchas versiones comerciales de Unix (como el AIX de IBM) donde los hilos son mucho más ligeros que los procesos, pero es menos efectivo en Linux donde los hilos y procesos son solo encarnaciones diferentes de la misma entidad del sistema operativo.

Más tarde, Apache añadió el event MPM, que amplía el MPM añadiendo un hilo escucha independiente que gestiona las conexiones de mantenimiento de inactividad una vez que la solicitud HTTP se ha completado.

Estas medidas sirven para mejorar el rendimiento, pero el modelo monolítico de un solo servidor que fue clave para el éxito temprano de Apache ha seguido luchando a medida que aumentan los niveles de tráfico y las páginas web se vuelven más y más pesadas.

El modelo pesado y monolítico de Apache tiene sus límites

El desempeño en los benchmarks de laboratorio a menudo no se reproduce en implementaciones de producción en vivo en sitios web ocupados y el ajuste de Apache para hacer frente al tráfico del mundo real es un arte complejo. De forma poco equitativa, Apache ha ganado una reputación como un servidor web hinchado, excesivamente complejo y limitado al rendimiento que puede ser explotado por ataques lentos de denegación de servicio (DoS).

Hasta aquí podemos saber un poco de historia de Internet como funcionan muchos de los servidores creados a día de hoy, ya que muchos utilizan Apache como base, sus problemas así como sus soluciones.

Introducción a NGINX - Diseñado para alta concurrencia

NGINX fue escrito específicamente para abordar las limitaciones de rendimiento de los servidores web Apache.

Fue creado en 2002 por Igor Sysoev, un administrador de sistemas para un popular sitio ruso (Rambler.ru), como una solución escalable para ayudar al sitio a administrar mayores y mayores volúmenes de tráfico.

Se convirtió en opensource en octubre de 2004, en el 47 aniversario del lanzamiento de Sputnik.

NGINX puede ser desplegado como un servidor web independiente, y como un proxy de frontend para Apache y otros servidores web.

Esta solución de despliegue actúa como un dispositivo de descarga de red frente a los servidores Apache, traduciendo las conexiones lentas del lado de Internet en conexiones rápidas y confiables del lado del servidor, y descargando completamente las conexiones keepalive de los servidores Apache.

El efecto neto es restaurar el rendimiento de Apache a niveles cercanos a los que los administradores ven en un punto de referencia local.

NGINX también actúa como un amortiguador de choque, protegiendo a los servidores vulnerables de Apache de picos en el tráfico y de ataques de conexión lenta como Slowloris y slowhttprequest.

El secreto en la arquitectura

El rendimiento y la escalabilidad de NGINX surgen de su arquitectura basada en eventos. Difiere significativamente del enfoque de proceso o hilo por conexión de Apache, en NGINX, cada proceso de trabajo puede manejar miles de conexiones HTTP simultáneamente. Esto resulta en una implementación considerada altamente como ligera, escalable y de alto rendimiento.

Nginx vs Apache

Para muchos tipos de aplicaciones, NGINX y Apache se complementan bien, por lo que suele ser más apropiado hablar de "NGINX y Apache" en lugar de "NGINX vs. Apache". Un patrón de inicio muy común es desplegar el software NGINX de código abierto como proxy (o NGINX Plus como plataforma de entrega de aplicaciones) frente a una aplicación web basada en Apache. NGINX realiza el levantamiento pesado relacionado con HTTP: sirve archivos estáticos, almacenamiento en caché de contenido y descarga de conexiones HTTP lentas, para que el servidor Apache pueda ejecutar el código de la aplicación en un entorno seguro.

NGINX proporciona todas las características principales de un servidor web, sin sacrificar las cualidades ligeras y de alto rendimiento que lo han hecho exitoso, y también puede servir como un proxy que reenvía las solicitudes HTTP a servidores web ascendentes (como un backend de Apache) y FastCGI, memcached, SCGI y uWSGI.

Nginx vs Apache

NGINX no busca implementar la enorme gama de funcionalidades necesarias para ejecutar una aplicación, sino que depende de servidores especializados de terceros como PHP-FPM, Node.js e incluso Apache.

“Apache is like Microsoft Word. It has a million options but you only need six. NGINX does those six things, and it does five of them 50 times faster than Apache.” — Chris Lea

Nginx vs Apache

Como podemos ver NGINX es un proyecto enfocado en la alta disponibilidad de los servicios, pero se enfoca en servir contenido estático y hacerla de proxy/conector entre otros servidores para darles mayor seguridad y ayudar a manejar el enorme trafico de usuarios.

Nginx vs Apache

Actualmente NGINX es utilizado por empresas como WIX, Wordpress y la NASA.

Es posible utilizar el soporte comunitario total mente gratis y también se tienen tres niveles de soporte de paga profesional ofrecido por NGINX, Inc.:

Básico ($1,900 US / year): Para los despliegues no críticos de NGINX en los que los tiempos de respuesta más largos son aceptables y no se requieren revisiones en caliente para implementaciones de producción.

Profesional ($3,000 US / year): Para el despliegue de producción de NGINX y NGINX Plus, o donde se requiera el manejo de casos prioritarios.

Enterprise ($4,500 US / year): Para despliegues de misión crítica de NGINX o NGINX Plus que requieren el nivel más alto de soporte con tiempos de respuesta más rápidos y soluciones a través de consultas telefónicas o web.

¿Qué cubre el soporte comercial de NGINX?

El uso de NGINX opensource y NGINX Plus

Búsqueda y corrección de errores.

Actualizaciones de software.

Asistencia con la instalación.

Notificaciones de seguridad.

Discrepancias en la documentación.

Nginx vs Apache

Conclusión:

Como podemos ver Apache como tal no esta muerto y no quiere decir que tengamos que dejar de utilizarlo y utilizar siempre NGINX, de hecho se recomienda utilizarlo junto con.

Simplemente es necesario combinarlo con los nuevos proyectos que están enfocados a solucionar problemas muy específicos en los cuales Apache o cualquier otro servidor no son buenos, en este caso NGINX, tiene los problemas más comunes de Apache resueltos pues así fue como nació.

También podemos ver que se tiene un soporte comercial y denota mucha madurez en el proyecto ya que es una empresa constituida y no es un proyecto que de la noche a la mañana pueda desaparecer, ver el peso y renombre de sus clientes da mucha confianza en ellos.

Fuentes:

https://www.nginx.com/blog/nginx-vs-apache-our-view/

» Leer más, comentarios, etc...

Jesús Perales

Iniciar un contenedor de docker automaticamente al reiniciar

octubre 07, 2017 07:31

Iniciar un contenedor de docker automaticamente al reiniciar

Es posible iniciar imagenes de Docker de una forma muy sencilla con simplemente ejecutar un docker start contenedor, pero al tener un servicio ya en un servidor productivo al reiniciarse vamos a querer que este inicie automaticamente.

Para hacer esto con systemd(algunos lo odian, otros lo aman y otros tantos simplemente trabajan con el) es necesario crear un archivo y enviarlo a la carpeta /etc/systemd/system/ y habilitarlo.

Aquí muestro un ejemplo con un contenedor llamado nexus

Contenido del archivo :

[Unit]
Description=Nexus container  
Requires=docker.service  
After=docker.service

[Service]
Restart=always  
ExecStart=/usr/bin/docker start -a nexus  
ExecStop=/usr/bin/docker stop -t 2 nexus

[Install]
WantedBy=default.target

Comandos a ejecutar:

Creamos el archivo:

touch docker-nexus.service

Lo abrimos con nano y pegamos el contenido de arriba ya personalizado:

nano docker-nexus.service

copiamos el archivo a la carpeta especial de systemd:

sudo cp docker-nexus.service /etc/systemd/system/

Lo habilitamos:

systemctl enable docker-nexus.service

Al reiniciar nuestor SO debería de levantarse automaticamente nuestro contenedor.
Fuentes:

https://docs.docker.com/engine/admin/host_integration/

» Leer más, comentarios, etc...

Jesús Perales

Gitlab, Rss y Thunderbird

octubre 07, 2017 07:29

Gitlab, Rss y Thunderbird

Actualmente me encuentro feliz usando Gitlab, solo tengo un pequeño detalle con el servidor SMTP de correos, para que lleguen las notificaciones a las personas cuando se mencionan, en fin uno nunca sabe cundo saldra un correo o no.

Aun así ya hay una petición para que se agregue un botón de prueba para enviar correos, puedes verla aquí.

Por ahora vamos a ver como configurar el rss de actividad de una cuenta personal de un usuario en Thunderbird.

Primero es necesario crearnos una cuenta de Canales web, tenemos que ir a Archivo -> Nuevo -> Otras cuentas

Gitlab, Rss y Thunderbird

Aparecerá una nueva ventana y seleccionamos la primera opción, damos siguiente.

Gitlab, Rss y Thunderbird

Ahora solo toca ponerle un nombre, puede dejarse por defecto, siguiente.

Gitlab, Rss y Thunderbird

Al para terminar damos click en Finalizar

Gitlab, Rss y Thunderbird

Ahora nos aparecerá una nueva cuenta en la parte derecha y debemos seleccionarla y dar click en Administrar suscripciones.

Gitlab, Rss y Thunderbird

Debemos buscar la URL del Rss de nuestra actividad, la cual podemos encontrar en nuestra cuenta de Gitlab en el menu y en la opcion activity, aparecerá en el lado izquierdo el icono de los Rss, lo presionamos.

Gitlab, Rss y Thunderbird

Nos llevará a una pagína, de la cual debemos copiar la URL y agregarla en Thunderbird en el input de URL del canal, al final solo presionamos añadir y cerrar.

Gitlab, Rss y Thunderbird

Gitlab, Rss y Thunderbird

Listo!, sigo creyendo que los correos son la solucion al problema, pero mientras busco como solucionarlo creo que esta opción es muy buena.

Gitlab, Rss y Thunderbird

Igual se podria utilizar Feedly para tu cuenta publica, pero no para cuando lo tienes montado en tu propio servidor.
Fuentes

https://support.mozilla.org/es/kb/cmo-suscribirse-noticias-y-blogs#w_paso-1-crear-una-cuenta-de-noticias-rss

» Leer más, comentarios, etc...

Jesús Perales

Adios 2016

octubre 07, 2017 07:22

Adios 2016

El 2016 fue un muy buen año para mi, aprendi muchas cosas y comence a darle mas enfoque a algunas que necesitaba y quiero resumir en este articulo una pequeña recapitulación.

Desafortunadamente murió Firefox OS y con el mi primera aplicación independiente(clima) , ya tiene tiempo que deje de darle mantenimiento, pero la página de Facebook sigue recibiendo visitas y "me gusta" a cuenta gotas, quizá la adapte para Android solo para no tener desperdiciada esa licencia de desarrollador que pague hace tiempo.

Comencé a crear algunos artículos sobre la configuración de Spring, los cuales tienen algunos errores sobre cuando utilizar @Repository y el @Service, necesito actualizarlos y agregar que ahora Spring puede configurarse con base a anotaciones, no mas XMLS!! , tengo pensado utilizar estos artículos para hacer mi primer libro sobre Spring, ese es uno de mis propósitos de año nuevo.

Después de que Google Drive eliminara el soporte para servir paginas y recursos de forma publica me quede sin CDN de imágenes, anteriormente utilizaba Cloudinary, pero decidí pasarme a Google Dirve y ahora creo que tendré que regresar y corregir todas las URLs, de todas formas tengo un respaldo de cada imagen en carpetas por articulo.

Estoy utilizando Openshift como hosting de mi blog, al parecer el equipo de desarrollo de Ghost comenzó a meter el acelerador en funcionalidades para el CMS y liberaba una versión por mes, al estar utilizando Openshift la actualización era de forma manual y al fallar el proceso de como actualizaba en la versión 0.9.0 deje de actualizarlo hasta que tome el control completo de mi nombre de dominio encontré una rama en Github con la ultima versión y utilice un nuevo Gear para que funcionará, saque un respaldo de la instancia anterior y funcionó, hasta configure el blog para que funcione con https lol, quizá cambié mi hosting a Digital Ocean a finales de este año haré algunas pruebas, por lo pronto Openshift es mas que suficiente.

Escribí sobre Git, el cual es un compañero de código para todo programador que se respete(Git o cualquier sistema de versionamiento de código), implemente Gitlab en mi trabajo actual, antes ya había utilizado otras herramientas que si bien son mas robustas son de paga y las empresas no siempre quieren arriesgarse a usar algo que nunca han probado.

Por ahora solo utilizo GNU/Linux en mi casa, en el trabajo solo soy un Windowsuser más, deje de utilizar Fedora , por que comparto mi Laptop y no le entendían a Gnome, por ahora estoy con Ubuntu 16.04 y funciona como debe ser.

También por fin quede atrapado en las garras de Android, mi celular con Firefox OS le dejo de funcionar el Wi-Fi y la cámara así que decidí adquirir uno nuevo con AT&T, el cual tiene un buen servicio y puedo jugar tranquilamente Pokemon GO.

Realice algunos experimentos con problemas relacionados a la visión computacional, el cual era un simple detector de Fuego, pero me ayudo a ver que aun nos falta mucho por recorrer en esto del Software.

No publicare las estadísticas del blog, por ahora, ya que son muy pocas las visitas que tengo.

Espero poder duplicar el numero de entradas escritas de este año que fueron 24, es decir 2 por mes y sería escribir una entrada por semana, escribiré un poco mas sobre el Frontend, Angular 1 y 2 , probar Vue y ver que tal me va con Sails y React que es lo que se esta moviendo ahora mismo, del Backend estoy pensando en hablar sobre Criteria, Spring Security, Jhipster, Hibernate Search, Lucene y Elastic Search.

Por ahora estoy pensando en publicar artículos no solo de programación, si no de política, finanzas y otros temas de los que también me gusta hablar, en México se aproximan las elecciones para presidente en el 2018 y en estas fechas se dará mucho de que hablar en torno a la política y como se que este blog lo leen personas de otros países creo que ayudará a no creer todo lo que pasan en CNN, en finanzas también se moverá mucho el tema, el dolar esta por los cielos y la inflación se disparará, en mi opinión podemos volver a caer en tiempos de Carlos Salinas de Gortari.

Quizá el articulo esta desfasado, pero ya tenia el borrador y no quise desperdiciar la oportunidad de desearles un buen 2017 a todas las personas que me leen.

¡Feliz 2017!

» Leer más, comentarios, etc...

Jesús Perales

Java 9

octubre 07, 2017 07:17

Java 9

Platicando con un amigo sobre Java 9 y que aun estamos trabajando con la versión 6, se nos ocurrio poner de cabeza a Eclipse para trabajar con la versión 9 y animarnos un poco.

Java 9

Fuera de broma, Java 9 ya está en camino, podemos ver que falta menos de un año para que salga a la luz en java9countdown y las nuevas funcionalidades que destacan, para mi, son :

  • Consola REPL(jshell).

  • Modulos.

  • Metodos por defecto en las interfaces.

  • Una nueva API para trabajar con JSON (JEP 198).

  • HTTP2.

  • WebSockets.

Fuentes:

» Leer más, comentarios, etc...

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Sponsors

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin