El blog de Juan Palómez

28 Octubre 2009

Copiar tablas / crear tablas con una SELECT / Copy tables

Archivado en: Uncategorized — Etiquetas:, , , , — thisisoneball @ 01:37

Hay varias formas diferentes de copiar una tabla rápidamente, en una sola consulta SQL. Evitan tener que hacer un CREATE TABLE especificando todos los nombres y tipos de datos de los campos. No obstante sólo copian los campos, no los índices ni las constraints. Esta es una comparativa de ellas y los SGBD en los que funciona cada una:

Consulta Mysql Postgres Oracle Access Copia estructura Copia datos
CREATE TABLE destino AS SELECT * FROM origen Si Si Si No Si Si
CREATE TABLE destino LIKE origen Si Si No No Si No
INSERT INTO destino SELECT * FROM origen Si Si Si Si No Si
SELECT * INTO destino FROM origen No Si* No Si Si Si

* La documentación de Postgres recomienda usar CREATE TABLE AS mejor que SELECT INTO

Esto también puede servir para copiar tablas entre distintos esquemas. Por ejemplo en MySQL:

CREATE TABLE esquema2.tabla AS SELECT * FROM esquema1.tabla;

 

18 Octubre 2009

La ranita feliz

Archivado en: Uncategorized — Etiquetas:, , , , — thisisoneball @ 15:00

Este es un programa que resuelve una de las pruebas del juego del MAME Puzzle & Action: Tant-R, en concreto la de la rana que tiene que ir pisando todas las casillas del tablero sin dejar ninguna sin pisar. Estas son algunas de las pantallas que te pueden salir:

tant0000 tantr tant0001

El programa muestra un tablero como el del juego pero vacío. Si pinchas en una casilla sitúas la rana, y las siguientes que pinches serán las piedras. Cuando hayas terminado de poner piedras pulsas Solución. Si hay más de una solución te seguirán saliendo cada vez que pulses el botón hasta que ya no queden más:

captura

Pincha aqui para bajar el ejecutable y el código

Puedes usar los casos que salen en el juego, como en las tres capturas de arriba, o probar otros.

Si vas a probar los tuyos propios muchas veces pasará que no haya ninguna solución, o que haya tantas que se te quede el programa prácticamente colgado pensando. Poniendo 5 o 6 piedras normalmente saca las soluciones rápido.

Realmente son dos programas separados:

  • Un programa en Lisp que es el que calcula las soluciones (rana.lsp). Recibe por la entrada estándar las posiciones de las piedras y de la rana: cada par de números son la coordenada X y la Y de una piedra, empezando de 0,0 que es la esquina superior izquierda. Luego una linea que ponga FIN, y luego las coordenadas de la rana.

    Al ejecutarlo imprime las soluciones con un dibujo en ascii, donde < > ^ v significa izquierda, derecha, arriba, abajo.

    Se puede ver todo esto en los ficheros entrada.txt, salida.txt y ejecutar.bat

  • Un frontend para el programa anterior, hecho en C + API de Windows
    Puedes usar ranita.exe para ejecutarlo o puedes abrir los ficheros de Visual Studio para editarlo. en ambos casos hace falta el clisp, que he incluido en una carpeta en el ZIP.

    Genera el fichero entrada.txt a partir de lo que dibuje el usuario, llama al programa Lisp para que calcule las soluciones, lee el fichero salida.txt y lo dibuja en la ventana.

  • 16 Octubre 2009

    MySQL Error 1083 SQLSTATE: 42000 (ER_WRONG_FIELD_TERMINATORS)

    Archivado en: Uncategorized — Etiquetas:, — thisisoneball @ 02:52

    Si estás usando la instrucción LOAD DATA INFILE en MySQL y te da este error sin motivo, reinicia el demonio de MySQL.
    (Puedes comprobar si realmente se ha quedado pillado con:
    LOAD DATA INFILE ‘nada.txt’ INTO TABLE nada;
    Esto debería dar un error de que la tabla no existe, en lugar del error anterior)

    Como norma general, si MySQL te está haciendo algo raro, reinícialo, casi siempre es culpa suya y no de tu código.

    If you’re using LOAD DATA INFILE in MySQL and you get this error and you think you shouldn’t, restart the MySQL daemon and it will work again.

    As a rule of thumb, if MySQL is behaving weird, just restart the daemon. Most of the times it’s its fault and not yours.

    16 Julio 2009

    Lugares de rodaje de películas

    Archivado en: Uncategorized — thisisoneball @ 23:20

    Movie filming locations

    Te gusta el cine? Te gusta viajar? Entonces te gustará visitar los lugares donde se rodaron tus películas favoritas. Esta es una recopilación de las mejores webs que he encontrado sobre este tema, tanto internacionales como españolas.

    Casi todas ellas permiten buscar por lugar (para cuando vayas a viajar allí) o por película (así puedes ver sólo los lugares de películas que hayas visto)

     

    Movie Locations Guide: http://www.movielocationsguide.com

    • Integrada con Google Maps. Para cada lugar muestra la localización en el mapa, y aparte tiene mapas de Nueva York, Los Ángeles y Londres con todos los lugares de rodaje de esas ciudades.
    • Tiene tanto películas como series
    • Tiene una captura de la película para cada lugar, para reconocer a qué escena se refiere exactamente
    • No se puede buscar por lugares excepto los 3 anteriores.

      

    The worldwide guide to movie locations: http://www.movie-locations.com

    • No está integrada con mapas pero suele mostrar la dirección. Ponen bastantes comentarios sobre las películas y sobre las escenas correspondientes a los lugares
    • Permite bsucar por películas, países, actores, directores, …
    • Tiene una captura de la película para cada lugar

     

    Filmaps: http://www.filmaps.com

    Es la versión Web 2.0 de este tipo de páginas. Los usuarios envían nuevos lugares, indicando película, coordenadas (también en Google Maps) y un comentario para indicar qué parte de la película se grabó en ese lugar. Lo del comentario está bien pero es mejor la captura que ponen las anteriores (se reconoce más fácilmente)

    Con el tiempo es de esperar que ésta o una de este estilo sea la que tenga más contenido, aunque de momento las anteriores para mí son mejores.

     

    Meipi Cinemadrid: http://www.meipi.org/cinemadrid.map.php

    Integrada con Google Maps, sólo para películas rodadas en Madrid

     

    Notodoestavisto.com:

    http://www.notodoestavisto.com/index.asp?ambito=4

    http://www.notodoestavisto.com/localizaciones1.asp

    • Películas. series y programas de TV, españolas o extranjeras
    • Tiene las coordenadas en Google Maps y también la dirección
    • Para cada lugar, tiene capturas de la película, fotos reales, un breve comentario y permite que los usuarios hagan sus comentarios

     

    World reviewer travel guides:

    http://www.worldreviewer.com/travel-guides/film-locations/

     

    Mi mapa:

    Esta es mi recopilación sacada de las webs anteriores. Recomendable para gente nacida sobre el 80 (Regreso al futuro, Matrix, Indiana Jones, …)

     http://www.google.com/maps/ms?ie=UTF8&hl=es&msa=0&msid=117541242875254911657.00046ed9e140f31091163&ll=51.508742,-43.59375&spn=84.069942,213.398438&z=3

     

     

    También hay libros dedicados a este tema, que son útiles para llevarlos encima cuando viajas. Puedes buscar por “movie locations” en cualquier tienda de libros online.

    26 Enero 2009

    Dependencias entre ficheros fuente

    Archivado en: Uncategorized — Etiquetas:, , , — thisisoneball @ 17:25

    Otra de las múltiples carencias de MySQL es que no tiene un sistema de dependencias entre objetos. Por ejemplo, un procedimiento almacenado que isnerta en una tabla, o lee de una vista, o una vista que lee de otras tablas o vistas, etc…

    Te permite borrar o cambiar de nombre un objeto aun cuando está siendo referenciado desde otro sitio. Esto hay quien lo ve como una ventaja (es más cómodo siempre que sepas lo que estás haciendo). Pero para un proyecto medianamente grande y en el que trabajen varias personas se hace inmantenible.

    Siempre queda la opción de hacer un programa que lea los ficheros fuente, y sepa encontrar ahí dónde se referencian otros objetos o ficheros fuente. He estado buscando en Internet algo hecho y solo he visto alguna cosa para ficheros .c y .h. Asi que he preparado este script como solución rápida. De momento solo busca en ficheros fuente de Perl y de procedimientos almacenados de MySQL.

    Va dibujando un arbol de las llamadas entre unos y otros. Por ejemplo:

    perl1.pl
    	perl2.pl
    	mysql1.sql
    	mysql2.sql
    		mysql4.sql
    	mysql5.sql

    en este caso el fichero perl1.pl llama a un programa Perl (perl2) y a 3 procedimientos almacenados (mysql1, mysql2 y mysql5), y mysql2 a su vez llama a mysql4

    Los parámetros del programa son el fichero fuente a partir del cual se empieza a buscar, y la lista de directorios en los que buscar, y se ejecuta desde el directorio que contiene a éstos. Ej:

    --raiz/
        |--perl/
            |-- perl1.pl
    	|-- perl2.pl
        |--mysql/
            |-- mysql1.pl

    En este caso se llamaría desde ‘raiz’, por ejemplo con:

    dependencias.pl perl1.pl {perl,mysql}

    Requiere el módulo File::Grep, que sirve para lo mismo que el comando grep. Es bastante incómodo de utilizar y probablemente habría sido mejor utilizar simplemente grep en un script de shell o de Perl.

    El programa es fácilmente adaptable a otros lenguajes. Solo hay que especificar cómo se hacen las llamadas a otros scripts (do, call, system, …)

    Se agradecen ampliaciones y correcciones.

    #!/usr/bin/perl
    
    use File::Grep qw( fgrep );
    
    die "Uso: dependencias.pl <archivo fuente inicial> {directorio1,directorio2}...\n\n" unless @ARGV == 2;
    
    my $directorios = $ARGV[1];
    
    busca($ARGV[0], "");
    
    sub busca {
    
    	my $ocurrencia;
    	my %matches;
    	my $match_key;
    	foreach $ocurrencia (fgrep { /(call|do).*\(/i } glob "$directorios/$_[0]") {
    		%matches = %{${$ocurrencia}{'matches'}};			# matches es un hash que contiene las lineas encontradas
    		foreach (sort keys %matches) {
    			if ($matches{$_} =~ m/call\s+(.+)\(/i) {		# llamadas a procedimientos almacenados MySQL
    				print "$_[1]$1.sql\n";
    				busca("$1.sql", $_[1]."    ");
    			} elsif ($matches{$_} =~ m/do.*\'(.+)\'/i) {		# llamadas a scripts Perl
    				print "$_[1]$1\n";
    				busca("$1", $_[1]."    ");
    			}
    		}
    	}
    
    }

    17 Enero 2009

    Ver videos 3GP en ACDSee

    Archivado en: Uncategorized — Etiquetas:, , , — thisisoneball @ 01:58

    View 3GP video files in ACDSee

    Se usas el ACDSee 2009 y estás buscando como ver formatos de vídeo que no están en la lista de los que soporta, se puede conseguir de varias formas.
    Este programa como muchos visores de imágenes para Windows reproduce todos los formatos para los que tengas códec instalado.
    Sin embargo tiene una lista de extensiones admitidas limitada y además no es configurable, lo cual es absurdo porque realmente podría abrir muchos más formatos.

    Se puede saltar esta limitación de dos formas:

  • Renombrando el video a una extensión admitida. Puedes dejar las dos extensiones, por ejemplo video.3gp.avi para saber cual es el formato real del archivo.
  • Si no quieres renombrar todos los archivos puedes editar el plugin para archivos de vídeo del ACDSee.
    Es el archivo C:\Archivos de programa\ACD Systems\ACDSee\11.0\PlugIns\ID_Media.apl (o en la carpeta que lo tengas instalado)
    Con un editor hexadecimal vas hasta la posición c1830, y ahí están las extensiones admitidas. Yo he cambiado MPA por 3GP y a la siguiente vez que abres el programa ya los reconoce. Por supuesto no puedes cambiar el tamaño total del archivo así que tienes que elegir una extensión que tenga el mismo número de caracteres.
  • 30 Noviembre 2008

    Extraer y reemplazar texto con expresiones regulares en MySQL

    Archivado en: Uncategorized — Etiquetas:, , , — thisisoneball @ 17:46

    Extracting and replacing text with MySQL regular expressions

    Entre las múltiples carencias de MySQL se encuentran (en parte) las expresiones regulares. Tiene el operador REGEXP que permite saber si una cadena casa con una expresión regular concreta, pero no permite extraer fragmentos de esa cadena ni reemplazar la cadena encontrada con otro texto.

    Esta es una función para MySQL que hace precisamente eso valiéndose del propio operador REGEXP.

    Es una chapuza rápida, probablemente no sea conveniente utilizarlo en determinados casos ya que debería ser bastante lenta (hace una llamada a REGEXP por cada subcadena contenida dentro de la cadena a buscar , por ejemplo, para buscar en ‘abc’, comprobaría la expresión regular con:

    a

    ab

    abc

    b

    bc

    c

    Si llamas a esta función dentro del select o el where de una consulta con muchos registros puede ser realmente ineficiente. He añadido DETERMINISTIC ya que la función es determinista, y en algunos casos de esta manera se puede ahorrar bastante tiempo de proceso.

    Uso:

    Devuelve la cadena más grande dentro de ‘cadena’ que coincida con ‘re’, por ejemplo

    SELECT substring_regexp(‘jket5657twr’, ‘[0-9]+’, NULL) –> 5657

    En este caso hay varias cadenas que coinciden con [0-9]+, por ejemplo 565, 5 , 657, … He decidido hacerlo de forma que devuelva la mayor de ellas.

    Para sustituir, se pasa como parámetro la cadena de sustitución, en vez de NULL:

    SELECT substring_regexp(‘jket5657twr’, ‘[0-9]+’, ‘HOLA’) –> jketHOLAtwr

    Código:

    DELIMITER $$
    
    DROP FUNCTION IF EXISTS `substring_regexp` $$
    CREATE FUNCTION `substring_regexp`(cadena text, re text, sustitucion text) RETURNS text CHARSET latin1 DETERMINISTIC
    BEGIN
    
    declare a, b, tam int;
    declare subcadena, retorno text;
    
    set tam = 0;
    set retorno = '';
    set a = 1;
    inicio: WHILE a <= length(cadena) DO
    	set b = a;
    	final: WHILE b <= length(cadena) DO
    		set subcadena = substring(cadena, a, b-a+1);
    		if subcadena regexp concat('^', re, '$') and length(subcadena) > tam then
    			set tam = length(subcadena);
    			if sustitucion is null then
    				set retorno = subcadena;
    			else
    				set retorno = replace(cadena, subcadena, sustitucion);
    			end if;
    		end if;
    		set b = b+1;
    	END WHILE final;
    	set a = a+1;
    END WHILE inicio;
    
    return retorno;
    
    END $$
    
    DELIMITER ;

    22 Octubre 2008

    Timoconcurso “cerillas”

    Archivado en: Uncategorized — Etiquetas:, , — thisisoneball @ 23:32

    Este es un programa en C que resuelve uno de los problemas de los “timoconcursos” de los canales de la TDT. En concreto el que aparece un número de 4 cifras hecho con cerillas y hay que conseguir el número más alto posible moviendo dos cerillas.

    Realmente este programa es algo más genérico: se puede especificar el número de cerillas que habría que mover, se pueden hacer con números de cualquier número de cifras, y devuelve todas las soluciones, no sólo el más alto.

    Esto es una curiosidad, no es para llamar al programa y tener la solución, porque el timo por lo visto está en que te tienen media hora al teléfono esperando para dar la solución, y eso en el caso de que cojan la llamada.

    NO LLAMES AL TIMOCONCURSO.

    #define	TRUE	1
    #define	FALSE	0
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <math.h>
    
    /*	tipo de datos "segmentos":
    		vector de 7 shorts, almacena un dígito en forma de segmentos, de la siguiente forma:
    			|-0-|
    			1   2
    			|-3-|
    			4   5
    			|-6-|
    		por ejemplo el 0 se almacenaría como:
    			segmentos numero0 = {1,1,1,0,1,1,1};
    
    	conjuntos de segmentos:
    		permiten almacenar un número completo compuesto de varios dígitos, por ejemplo para almacenar números de 4 dígitos:
    			segmentos numero[4];
    		para hacer lo mismo en tiempo de ejecución:
    			segmentos *numero;
    			numero = (segmentos *) calloc(4, sizeof(segmentos));
    */
    typedef short segmentos[7];
    
    //	especificación de los dígitos del 0 al 9 en forma de segmentos (vector de 10x7 shorts)
    segmentos digitos[10] = {
    	{1,1,1,0,1,1,1},
    	{0,0,1,0,0,1,0},
    	{1,0,1,1,1,0,1},
    	{1,0,1,1,0,1,1},
    	{0,1,1,1,0,1,0},
    	{1,1,0,1,0,1,1},
    	{1,1,0,1,1,1,1},
    	{1,0,1,0,0,1,0},
    	{1,1,1,1,1,1,1},
    	{1,1,1,1,0,1,1}
    };
    
    //	copia los 7 segmentos de un dígito
    void CopiarSegmentos(segmentos destino, segmentos origen)
    {
    	memcpy(destino, origen, 7 * sizeof(destino[0]));
    }
    
    //	transforma un entero "origen" a un vector de segmentos "destino" (no un solo dígito sino un número completo compuesto de "tam" dígitos)
    void IntSegmento(segmentos *destino, int origen, int tam)
    {
    	while (tam >= 1) {
    		tam--;
    		CopiarSegmentos(destino[tam], digitos[origen % 10]);
    		origen = origen / 10;
    	}
    }
    
    /*	compara dos números de "tam" dígitos
    	devuelve TRUE si entre los dos se han movido "ndiferencias" segmentos
    */
    int CompararSegmentos(segmentos *origen, segmentos *destino, int tam, int ndiferencias)
    {
    	int i;
    	int diferencias = 0, total = 0;
    
    	for (i=0; i < tam*7; i++) {
    		total += (*origen)[i] - (*destino)[i];
    		if ((*origen)[i] != (*destino)[i])
    			diferencias++;
    	}
    
    	// total es la diferencia en el total de segmentos, y debe ser 0 (mismo nº de cerillas en los dos casos)
    	// diferencias es el total de segmentos cambiados (es el doble del nº de cerillas movidas)
    	if (diferencias == ndiferencias*2 && total == 0)
    		return TRUE;
    	else
    		return FALSE;
    }
    
    /*	parámetros del programa: el número inicial y el número de cerillas que hay que mover
    
    	retorno:
    		0	sin errores
    		1	error en el número de parámetros pasados
    		2	error en el formato de los parámetros
    */
    int main(int argc, char *argv[])
    {
    	int inicial, ndiferencias, tam; segmentos *s_inicial;
    	int candidato; segmentos *s_candidato;
    
    	// gestión de parámetros de entrada
    	if (argc != 3) {
    		fprintf(stderr, "Uso: %s\nEjemplo: %s 5912 2\n", argv[0], argv[0]);
    		return 1;
    	}
    
    	inicial = atoi(argv[1]);		// ej: 5912
    	ndiferencias = atoi(argv[2]);		// ej: 2
    	if (inicial == 0 || ndiferencias == 0) {
    		fprintf(stderr, "Los parámetros tienen que ser numéricos y distintos de 0\nEjemplo: %s 5912 2\n", argv[0]);
    		return 2;
    	}
    
    	tam = strlen(argv[1]);			// ej: 4
    	candidato = (int) pow(10,tam) - 1;	// ej: 9999
    
    	s_inicial = (segmentos *) calloc(tam, sizeof(segmentos));
    	s_candidato = (segmentos *) calloc(tam, sizeof(segmentos));
    
    	// pasar el nº inicial a segmentos y comparar con cada nº candidato decrecientemente
    	IntSegmento(s_inicial, inicial, tam);
    	while (candidato >= 0) {
    		IntSegmento(s_candidato, candidato, tam);
    		if (CompararSegmentos(s_inicial, s_candidato, tam, ndiferencias))
    			printf("%d\n", candidato);
    		candidato--;
    	}
    
    	return 0;
    }

    17 Junio 2008

    Calcular distancias entre dos puntos dados en grados

    Archivado en: Uncategorized — Etiquetas:, , , , — thisisoneball @ 16:20

    Las coordenadas latitud/longitud vienen en grados, con lo cual no se pueden medir distancias. En cambio el sistema UTM sirve también para localizar un punto concreto en la superficie de la Tierra, y viene expresado en metros. Con lo cual solo hace falta pasarlas de un sistema a otro.
    Este código (Perl) pasa las coordenadas a UTM con el módulo Geo-Coordinates-UTM y luego calcula la distancia con el teorema de Pitágoras. Previamente tienes que tener las coordenadas de un punto en $latitud1 y $longitud1 y las del otro en $latitud2 y $longitud2:


    use Geo::Coordinates::UTM;...

    ($zona, $este1, $norte1) = latlon_to_utm ("WGS-84", $latitud1, $longitud1);
    ($zona, $este2, $norte2) = latlon_to_utm ("WGS-84", $latitud2, $longitud2);
    $distancia = sqrt((($este2 - $este1)**2) + (($norte2 - $norte1)**2));

    De forma manual se puede usar simplemente la regla de Google Earth. El código anterior es útil si tienes que procesar series grandes de coordenadas.
    Se puede configurar Google Earth para que muestre las coordenadas en UTM en vez de en grados: Herramientas > Opciones > Vista 3D > Universal Transversal de Mercator.

    Creo que se puede calcular sin pasar a UTM, con las fórmulas de geometría de la esfera. Tendría cierto error ya que la Tierra no es esférica, pero para distancias pequeñas no debería importar.

    Problema: El sistema UTM va por cuadrantes. Este código sólo sirve entre dos coordenadas que estén en el mismo cuadrante.

    13 Junio 2008

    Sincronizar hora por HTTP

    Archivado en: Uncategorized — Etiquetas:, , , , — thisisoneball @ 01:00

    Esto sirve para casos en los que no se pueda usar NTP. En mi caso los dispositivos que tengo que sincronizar no dejan conectar al puerto de NTP (123), solo puede conectar a los puertos de HTTP y FTP, así que lo hago a través de HTTP.
    Los servidores web devuelven la fecha en las cabeceras HTTP, por ejemplo:

    HTTP/1.1 200 OK
    Date: Thu, 12 Jun 2008 22:27:47 GMT
    Server: Oracle-Application-Server-10g/10.1.2.2.0 Oracle-HTTP-Server
    Last-Modified: Mon, 31 Mar 2008 17:00:37 GMT
    ETag: “6555ee-168-47f118b5″
    Accept-Ranges: bytes
    Content-Length: 360
    Connection: close
    Content-Type: text/html

    Así que se puede coger la fecha de ahí. Esto lo hace un programa llamado Htpdate:
    http://www.clevervest.com/twiki/bin/view/HTP/WebHome
    El código que pongo aquí es para Windows Mobile, ya que Htpdate no tiene versión para este sistema. Es bastante más simple que dicho programa, pero funciona.


    SYSTEMTIME HTTPGetTime(LPTSTR server)
    {
    HINTERNET session, connection, request;
    SYSTEMTIME time; DWORD size;

    session = InternetOpen (L"HTTPGetTime", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    connection = InternetConnect (session, server, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, NULL);
    request = HttpOpenRequest (connection, L"GET", L"/", NULL, NULL, NULL, NULL, NULL);
    HttpSendRequest (request, NULL, 0, NULL, 0);
    size = sizeof(time);
    HttpQueryInfo (peticion, HTTP_QUERY_DATE | HTTP_QUERY_FLAG_SYSTEMTIME, &time, &size, NULL);
    InternetCloseHandle(request);
    InternetCloseHandle(connection);
    InternetCloseHandle(session);

    return time;
    }

    El único parámetro que toma la función es el hostname de un servidor web. Sirve cualquiera (que esté en hora), pero es mejor usar uno lo más cercano posible.
    La mecánica es bastante simple, conecta al servidor, hace una petición GET, recibe los datos y coge la fecha de las cabeceras HTTP. Las funciones de WinInet hacen casi todo el trabajo.
    Esto está probado en Windows Mobile 5 pero debería funcionar en cualquier Windows.

    La función devuelve la hora en GMT. Para sincronizar la hora del sistema sería por ejemplo así:


    SYSTEMTIME time;

    time = HTTPGetTime(L"www.rediris.es");
    SetSystemTime(&time);

    No es realmente exacto por el retardo que hay desde que el servidor devuelve la hora hasta que se cambia la hora del dispositivo, pero es bastante exacto. Como dice el autor de Htpdate, si puedes hazlo por NTP y si no por HTTP.

    Posibles mejoras:

    • comprobación de los códigos de retorno de las funciones
    • usar HEAD en vez de GET: no hace falta bajar toda la pagina solo para leer las cabeceras
    Entradas más antiguas »

    Blog de WordPress.com.