Descargar una web completa

Existe una forma que permite descargar toda una web a una carpeta almacenada de forma local a través del comando wget. En MacOS este comando no viene de serie así que lo instalé a través de Homebrew:
$ brew install wget
En muchas distribuciones de Linux, sin embargo, viene ya con el sistema operativo.

Y ahora para descargar toda una web:
$ wget \
--recursive \
--no-clobber \
--page-requisites \
--html-extension \
--convert-links \
--restrict-file-names=windows \
--domains www.mipagina.com \
--no-parent \
http://www.mipagina.com/subdirectorio/html/

Donde las opciones son:
--recursive: descargar la página web completa.
--domains www.mipagina.com: no descargar nada que no pertenezca a este dominio.
--no-parent: no seguir enlaces fuera de la ruta /subdirectorio/html/.
--page-requisites: descargar todos los recursos necesarios (imágenes, CSS, etc.)
--html-extension: guardar archivos con extensión .html
--convert-links: modificar todos los enlaces para que funcionen de forma local.
--restrict-file-names=windows: modificar nombres de ficheros para que funcionen correctamente bajo Windows (si éste es tu SO).
--no-clobber: no sobreescribir archivos existentes (por si se corta la descarga y vuelves a ejecutar el comando, para que no baje cosas que ya tienes).

Aunque este truco le resultará familiar a todos los fans del intérprete de comandos, pocas veces lo he necesitado y quiero dejarlo por aquí apuntado para un futuro.

Transferir archivo a servidor mediante SCP

Hoy me he encontrado con la siguiente situación: quería transferir un archivo a desde un ordenador con Windows a mi Mac a través de la red local. Hay unas cuantas formas de hacerlo pero la primera que se me ha ocurrido ha sido mediante el protocolo SCP, que es suficientemente rápido y fácil como para solucionar el problema sin demasiadas complicaciones.

En primer lugar, hay que configurar el Mac para que podamos hacer login remoto (por SSH). Esto se hace activando la opción de «Sesión Remota» en la categoría «Compartir» de las Preferencias del Sistema:

Es importante que en esta pantalla hagamos clic sobre el símbolo «+» y añadir nuestro usuario de MacOS.
En el lado del Mac (que actuará como servidor) ya está todo listo. Vamos a Windows.

Un buen cliente de SCP es PSCP, del mismo desarrollador de la utilidad Putty. Basta con descargar el ejecutable necesario para tu plataforma ya que no requiere instalación:

Por comodidad, lo he descargado al escritorio, aunque si lo vas a usar habitualmente puede que te resulte más cómodo moverlo a otra ubicación que se encuentre en el PATH del Símbolo de sistema (p.ej. a C:\Windows\System32\).
Ahora en mi escritorio tengo el ejecutable y el archivo que quiero transferir:

Ya sólo queda ejecutar el comando que realizará la transferencia. Desde un intérprete de comandos, me posiciono en el Escritorio y lanzo la siguiente orden:
$ pscp -P 22 test.zip emilio@192.168.1.35:/Users/emilio

El flag -P establece el puerto de la conexión (por defecto el 22 para conexiones SSH), y después del nombre del fichero establecemos el destino: usuario «emilio» en la IP local de mi Mac, seguido de dos puntos y una ruta donde este usuario tiene permisos de escritura (por ejemplo, su carpeta personal). Si es la primera vez que ejecutas este comando, se te preguntará si confías en el host (y se mostrará su firma criptográfica):

Cabe resaltar que en lugar de la IP del Mac, puedo modificar el comando y referirme a él por su nombre de host local, e incluso que el destino sea otra carpeta como la de descargas:
$ pscp -P 22 test.zip emilio@MacBook-Pro:/Users/emilio/Downloads/

Las siguientes veces que te conectes a esta máquina no se te pedirá confirmar su clave criptográfica ya que Putty las almacena en el registro de Windows. Si quieres comprobarlas (o incluso eliminarlas) las encontrarás bajo la ruta:
HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys

En MacOS (y creo que también en Linux) tenemos ya por defecto disponible un cliente equivalente en el terminal, llamado «scp». El comando funciona con la misma sintaxis y de forma prácticamente idéntica. Las claves quedan luego almacenadas en este fichero:
~/.ssh/known_hosts

🙂

Problema Productor Consumidor (Java)

He desarrollado un pequeño código a través del cual poder entender y experimentar con el escenario conocido como «problema de productor-consumidor», que tiene que ver con sincronización de hilos y acceso a memoria.

Para ponernos en contexto, pensemos en un programa que lanza dos procesos (o más) que deben escribir y consultar el contenido de una variable de forma repetitiva.
El proceso que genera los datos (el productor) no puede seguir escribiendo nuevos datos mientras la variable aun contenga el dato anterior; y el proceso que los consulta (el consumidor) no debe obtenerlos mientras no hayan sido actualizados (o mientras no hayan empezado a generarse).
La variable puede ser cualquier cosa, no necesariamente una tipo de dato simple. Podemos estar hablando incluso de estructuras abstractas de datos: listas, pilas, etc.

La forma de resolver todo esto se basa en un mecanismo sencillo, aunque no trivial. Veámoslo por partes.

Los datos
Para este ejemplo voy a desarrollar una clase llamada InfoBuffer, que simplemente contendrá un valor entero. Este valor será privado y solo se puede consultar a través de los métodos put y get.
Una variable llamada «isWritable» actúa como bandera (flag) para señalar si la variable está esperando para ser escrita o para ser leída. Inicialmente la ponemos como verdadera, para que el primero en actuar sea el productor.

Lo realmente importante está en el hecho de que los métodos get y put son métodos sincronizados («synchronized»). Marcarlos como tal hace que mientras uno se ejecuta en un hilo, el resto de métodos quedan bloqueados para esa instancia de esa clase (se mantiene su ejecución en espera).
Es decir, si yo tengo un objeto de tipo InfoBuffer y estoy leyendo (get) en un hilo, otros hilos deben esperar si están intentando realizar operaciones que estén también marcadas como «synchronized». Esto previene que los métodos intenten acceder de forma concurrente a la misma variable, y que cada uno espere a que el anterior termine.

En el caso del método put, comprobamos si la variable está en modo escritura. Si no lo está, hacemos que el hilo espere.
Cuando esté, salimos del bucle while, cambiamos el valor y la ponemos en modo de lectura (!isWritable).
El método get realiza las mismas operaciones pero con la comparación opuesta y retorna el valor almacenado.

El productor
La clase productor debe implementar la interfaz Runnable para poder lanzarla más adelante en un hilo separado. Algo similar podría obtenerse haciendo que extienda la clase Thread, pero yo he preferido hacerlo así. Para construir un productor, debemos pasarle el objeto InfoBuffer sobre el que estará escribiendo.
El método run contiene un bucle con 10 pasos en los que tratará de almacenar un valor en el objeto InfoBuffer.

El consumidor
La clase que consume datos funciona de forma análoga al productor. En este caso el bucle ejecuta 10 veces la función get para mostrar los datos en pantalla.

Programa principal
El programa principal sólo tiene que crear un objeto InfoBuffer, un productor y un consumidor, dándoles la referencia del primero. Ambos procesos se lanzan en hilos separados.

Output
El programa se puede compilar mediante la orden de terminal:
$ javac ProducerConsumer.java
Se ejecuta con:
$ java ProducerConsumer
O directamente, sin compilar ni nada, se puede hacer:
$ java ProducerConsumer.java
(Esto sólo funciona si todas las clases están escritas en el mismo archivo pero la primera es la clase pública que contiene el método «main»).

La salida es:

Y se puede ver cómo el programa va escribiendo diferentes valores en cada paso del bucle de escritura, alternándose con las operaciones de lectura de esos valores de forma ordenada a pesar de haber sido lanzadas desde dos hilos concurrentes.

Documentación
Puedes generar documentación del programa «parseando» los comentarios escritos en el código si ejecutas la orden:
$ javadoc ProducerConsumer.java -package -d help
Esto generará un directorio llamado «help» con unos cuantos archivos dentro. Si abres el archivo «index-all.html» en tu navegador, verás algo como esto:

Código completo

El código completo se puede descargar en este enlace:
https://github.com/emilio-devesa/producerconsumer/archive/refs/heads/main.zip

Custom zsh shell (MacOS)


El intérprete de terminal de comandos por defecto en MacOS desde hace un par de años (concretamente desde la versión 10.15 Catalina) es Zsh. Éste intérprete viene configurado para mostrar un aspecto realmente limpio y sobrio, con texto negro sobre fondo blanco (o viceversa cuando estamos usando el modo oscuro del entorno gráfico). Lo cierto es que quise saltarme esa apariencia tan austera y poder al menos darle un pequeño toque de color que además facilitase la lectura cuando llevas un rato ejecutando comandos.
En particular, he puesto mi nombre de usuario y el de la máquina en color verde, seguido del directorio actual en azul; y para ello he tenido que recurrir al archivo de configuración «.zshrc» que debería estar alojado en el directorio de usuario. Si no lo tienes, debes crearlo. Si ya lo tienes, basta con que añadas al principio un par de líneas:
autoload -U colors && colors
PS1="%F{green}%n@%m %F{blue}%1~ %# %{$reset_color%}"

Si tienes el terminal ya abierto, puedes forzar que recargue la configuración sin necesidad de cerrarlo y volver a abrirlo. Ejecuta este comando:
$ source ~/.zshrc
Listo! Mucha información la he encontrado en Stack Overflow:
https://stackoverflow.com/questions/689765/how-can-i-change-the-color-of-my-prompt-in-zsh-different-from-normal-text

Instalar servidor Apache Tomcat en MacOS

Muy fácil! Primero nos dirigimos a la página web oficial de Apache Tomcat y nos descargamos la versión que queramos, seguramente la última:

En este caso, la última version estable publicada es la 10.0.20.
De todos los paquetes disponibles, nos bajamos simplemente el que viene como fichero zip:

En mi caso, se ha descargado en la carpeta de Descargas de mi Mac y ya se ha descomprimido automáticamente, por lo que basta con abrir el terminal y trasladarlo todo a una ubicación más apropiada:
$ sudo mv ~/Downloads/apache-tomcat-10.0.20 /usr/local/tomcat
Ahora, establecemos nuestro usuario como propietario (cambia mi nombre por tu nombre de usuario):
$ sudo chown -R emilio /usr/local/tomcat
Y por último damos permisos de ejecución a los binarios:
$ sudo chmod +x /usr/local/tomcat/bin/*.sh

Listo, ahora el servidor se puede arrancar y parar mediante los siguientes comandos:
$ /usr/local/tomcat/bin/startup.sh
$ /usr/local/tomcat/bin/shutdown.sh

Y si está corriendo podemos acceder a los contenidos en local a través de http://localhost:8080. Como ejemplo, mostrará la página que puedes ver en la siguiente imagen: