Archivo

Entradas Etiquetadas ‘datos’

Automatizando la publicación de nuestras páginas de Facebook

Lunes, 6 de Febrero de 2012 Gaspar Fernández Sin comentarios

facebook_1
Si administramos páginas de Facebook, en ocasiones nos interesa introducir contenidos procedentes de diversas fuentes, tal vez un RSS (o varios) (Por ejemplo, si administras la página de un blog, o quieres mostrar noticias), eso sí, de fuentes confiables. O tal vez quieres publicar un mensajes varias veces a lo largo de un día, o varios mensajes de forma escalonada en el tiempo.

Objetivos

El problema es que esto nos obliga a estar constantemente pendientes de esas publicaciones. En mi caso, cuando tengo tiempo, sí puedo entrar en mis páginas de Facebook para hacer publicaciones, pero me gustaría que esas publicaciones también fueran aprovechables para mis visitantes en Hispanoamérica, cuyos horarios son muy diferentes a los míos, así como para gente que suele conectar más por la mañana o por la tarde. Además, en ocasiones me interesa publicar varios mensajes en alguna de las páginas, pero quiero ver antes la reacción que tienen entre los usuarios (Comentarios y Me Gusta), sin ser demasiado pesado (cuando veo muchas publicaciones seguidas de una misma fuente me agobio y siento la tentación de dejar de recibir publicaciones de esa fuente).

Para solucionarlo, propongo montar una aplicación web (lo que muestro aquí es sólo un esbozo, ya que podemos hacerla tan compleja como queramos), para montar esta aplicación, aprovecharé información que ya he puesto en algún post anterior.

Mi objetivo es poder administrar todas mis páginas de Facebook desde un sitio centralizado, sin tener que entrar una a una modificando las publicaciones.

Base de datos de la aplicación

Como hemos visto en el post anterior, para publicar en nombre de una página, debemos almacenar los access tokens que dan permiso a la aplicación para publicar como si fuera esa página de Facebook. Además, debemos almacenar los IDs de páginas de Facebook para que la aplicación sepa quién va a publicar y dónde (en el muro de dicha página)

Para ello voy a utilizar el motor MySQL y así podremos subir la aplicación a cualquier hosting que soporte este motor. En la base de datos (facebook_autopublish), crearemos varias tablas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE TABLE `facebook_autopublish`.`paginas` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`name` VARCHAR( 128 ) NOT NULL ,
`access_token` VARCHAR( 255 ) NOT NULL ,
`category` VARCHAR( 64 ) NOT NULL ,
`fcbkId` VARCHAR( 32 ) NOT NULL ,
`last_update` BIGINT NOT NULL
) ENGINE = InnoDB;

ALTER TABLE `paginas` ADD INDEX ( `last_update` ) ;

CREATE TABLE `facebook_autopublish`.`publish` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`pageID` INT UNSIGNED NOT NULL ,
`time` BIGINT NOT NULL ,
`message` TEXT NOT NULL ,
PRIMARY KEY ( `id` ) ,
INDEX ( `id` )
) ENGINE = InnoDB;

ALTER TABLE publish ADD FOREIGN KEY ( pageID ) REFERENCES paginas( id ) ;

La tabla paginas contendrá lo siguiente:

  • id : identificador de la página para la aplicación que estamos desarrollando
  • name : nombre de la página, obtenido de Facebook
  • access_token : access_token de la página, obtenido de Facebook
  • category : categoría de la página, obtenida de Facebook
  • fcbkId : ID de la página en Facebook, nos servirá para saber dónde publicar
  • last_update : momento en el tiempo de nuestra última publicación en la página

La tabla publish contendrá lo siguiente:

  • id : Identificador de la publicación para la aplicación
  • pageID : Id de la página donde se va a publicar
  • time : Momento en el tiempo en que queremos publicar
  • message : Texto de la publicación

La primera tabla (paginas), se generará automáticamente al cargar la página de identificación en Facebook. Para la segunda tabla (publish), debemos crear un interfaz web o CLI que vaya introduciendo los mensajes que tenemos que ir publicando. Ese interfaz no lo voy a incluir en este ejemplo, podemos incluso hacerlo desde PHPMyAdmin, o desde el comando mysql.

Script de identificación

Este script contendrá la autorización de la aplicación, y el llenado de la tabla paginas con los datos extraídos de Facebook (los ID de página y los access_tokens son lo más importante), y tenemos que almacenarlos en base de datos, para ello podemos usar el siguiente script:

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
<?php

$mysql_host = 'Host Mysql';
$mysql_user = 'Usuario MySQL';
$mysql_pass = 'Pass MySQL';
$mysql_db   = 'facebook_autopublish';

$api_key = 'API Key';
$api_sec = 'API Secret';

require_once('facebook_ext.php');
// Definimos constantes
define(NEEDED_PERMISSIONS,  'publish_stream,offline_access,manage_pages');
define('_scampo', '`');
define('_svalor', '\'');

// Definimos códigos de error
define(NOT_INSTALLED,      1);
define(NO_PERMISSIONS,     2);
define(NO_MYSQL_CONN,      3);
define(NO_PAGE_DATA,       4);
define(MALFORMED_ARRAY,   90);

// Funciones para el manejo de la bdd
function escapa($dato, $char, $numeric=true)
{
  if (($numeric) && (is_numeric($dato)))
    return ($dato+0);       /* Si es 0, no devolverá FALSE */
  else
    return $char.mysql_real_escape_string($dato).$char;
}

function valor($valor, $numeric=true)
{
  return escapa($valor, _svalor, $numeric);
}

function campo($valor, $numeric=true)
{
  return escapa($valor, _scampo, $numeric);
}

function insert($tabla, $datos)
{
  $uso='
INSERT';

  $sql=$uso.'
INTO '.campo($tabla). '(';
  $values='
VALUES (';

  $campos=array_keys($datos);
  $ndatos=count($campos);
 
  for ($i=0; $i<$ndatos; $i++)
    {
      $campo=$campos[$i];
      if ($i)
    {
      $sql.=", ";
      $values.=", ";
    }

      $valor = $datos[$campo];
      $sql.=campo($campo);
      $values.= valor($valor);
    }
  $values.=")";
  $sql.=") ".$values.";";

  $res = mysql_query($sql);
  // Depuración
  echo "Ejecutando: ".$sql.'
<br/>';
  return ($res)?mysql_insert_id():false; /* Devolver el ID del elemento insertado */
}

function truncate($table)
{
  return mysql_query("TRUNCATE TABLE ".campo($table));
}

try
{
  if (!mysql_connect($mysql_host, $mysql_user, $mysql_pass))
    throw new Exception('
No puedo conectar con el servidor MySQL', NO_MYSQL_CONN);

  // Generar otra excepción por si las moscas.
  mysql_select_db($mysql_db);

  $facebook = new FacebookExtended(array(  
                     '
appId'  => $api_key,
                     '
secret' => $api_sec,
                     '
cookie' => true ,
                       ));

 
  $sesion = $facebook->getUser();
  if (!$sesion)
    throw new Exception('
Aplicación no instalada', NOT_INSTALLED);

  echo "Estamos identificados en Facebook<br/>";
  echo "Usuario: ".$sesion."<br/>";

  $permissions = $facebook->askForPermissions(NEEDED_PERMISSIONS);
   
  if (!$permissions)
    throw new Exception('
No tengo permisos suficientes', NO_PERMISSIONS);
 
  $pagedata=$facebook->api('
/me/accounts');

  if ( (!$pagedata) || (!isset($pagedata['
data'])) || (count($pagedata['data'])==0) )
    throw new Exception('
No he recibido información de páginas', NO_PAGE_DATA);

  truncate('
paginas');

  $npages = count ($pagedata['
data']);
  for ($i=0; $i<$npages; $i++)
    {
      $pdata=$pagedata['
data'][$i];
      // Depuración
      echo "Insertando pagina: ".$pdata['
name']."<br/>";

      if (!insert('
paginas', array('name'         => $pdata['name'],
                   '
access_token' => $pdata['access_token'],
                   '
category'     => $pdata['category'],
                   '
fcbkId'       => $pdata['id'],
                   '
last_update'  => 0)) )
    echo mysql_error()."<br/>";
    }

}
catch (FacebookException $e)
{
  echo "Error de Facebook: ".$e->getCode().": ".$e->getMessage();
}
catch (Exception $e)
{
  switch ($e->getCode())
    {
    case NOT_INSTALLED:
      $facebook->loginUser();
      break;
    case NO_PERMISSIONS:
      $facebook->loginUser(NEEDED_PERMISSIONS);
      break;

    default:
      echo "Ocurrió un error: ".$e->getMessage();
    }
}
?>

Publicando mensajes automáticamente

Ahora, sólo tenemos que ir insertando entradas dentro de la tabla publish, especificando el momento en el que se va a insertar en el campo time y el ID de la página donde se va a insertar (ID dentro de la aplicación, no dentro de Facebook, se puede mirar en la tabla paginas)

Asimismo la aplicación buscará mensajes nuevos para publicar y que no haga menos de dos horas (configurable) desde el último mensaje publicado en la página especificada. Para ello tenemos el siguiente código (publica.php)

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
<?php

$mysql_host = 'MySQL Host';
$mysql_user = 'MySQL User';
$mysql_pass = 'Password';
$mysql_db   = 'facebook_autopublish';

$api_key = 'xxxxxxxxxxxx';
$api_sec = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

require_once('facebook_ext.php');

// Definimos constantes
// Tiempo mínimo entre publicaciones, 7200 segundos = 2h
define(MIN_TIME_BET_PUB,   7200);

// Definimos códigos de error
define(NOT_INSTALLED,      1);
define(NO_PERMISSIONS,     2);
define(MALFORMED_ARRAY,   90);
define(BAD_MYSQL,          3);

try
{
  if (!mysql_connect($mysql_host, $mysql_user, $mysql_pass))
    throw new Exception('No puedo conectar con el servidor MySQL', NO_MYSQL_CONN);

  // Generar otra excepción por si las moscas.
  mysql_select_db($mysql_db);
 
  $campos='paginas.id as pID, publish.id as pubId, paginas.fcbkId, paginas.access_token, publish.message';

  $sql='SELECT '.$campos.' FROM `publish` LEFT JOIN `paginas` ON publish.pageID=paginas.id WHERE last_update<'.(time()-MIN_TIME_BET_PUB).' ORDER BY last_update ASC LIMIT 1';
  $res=mysql_query($sql);
  if (!$res)
    throw new Exception('Hubo un error en la sentencia MySQL ('.$sql.'): '.mysql_error(), BAD_MYSQL);

  // Si se ha devuelto algo...
  if (mysql_num_rows($res)>0)
    {
      $data = mysql_fetch_assoc($res);

      $facebook = new FacebookExtended(array(  
                         'appId'  => $api_key,
                         'secret' => $api_sec,
                         'cookie' => true ,
                           ));

      $destino=$data['fcbkId'];
      $access_token=$data['access_token'];
      $public=$data['message'];

      print_r( $facebook->api('/'.$destino.'/feed', 'post', array('access_token' => $access_token,
                                  // 'uid' => $destino,
                                  'message' => $public)));  

      $sql = 'UPDATE paginas SET last_update='.time().' WHERE id=1';
      mysql_query($sql);

    }
} catch (Exception $e)
{
  echo "Ocurrió un error: ".$e->getMessage();
}
?>

Ejecutándolo en el tiempo

El objetivo de estos scripts es poder ejecutarlos cada cierto tiempo y, si usamos un sistema UNIX podemos hacer que cada cierto tiempo (cada 2h, por ejemplo), se haga una petición a la página, para ver si tenemos mensajes nuevos pendientes de publicación, y si hay, que los publique (publica.php ya se encarga de eso).

Para añadir el programa en nuestro cron, debemos ejecutar en un terminal:

$ crontab -e

Y allí escribir lo siguiente:

15 */2 * * * wget http://dominio.xxx/carpeta/publica.php

Donde cambiamos la dirección especificada por la dirección donde hemos instalado el script publica.php. Esto hará que se ejecute cada 2h en el minuto 15, todos los días, todos los meses y todos los días de la semana.

Consideraciones de seguridad

Es una buena idea hacer que index.php sólo podamos ejecutarlo nosotros desde nuestra sesión de Facebook, restringiendo la ejecución a nuestro ID de usuario. Así evitamos que la base de datos se pueble con páginas que no queremos, y también estaría bien restringir esa página con contraseña desde Apache, así evitamos sorpresas.

También es conveniente no hacer TRUNCATE TABLE todo el rato, sino ver y comprobar que los access_tokens no cambian (lo dejamos así, porque en ocasiones, Facebook los ha cambiado, reiniciado, o éstos han caducado pasados unos meses).

En la parte de publica.php sería conveniente el envío de una contraseña, para que no pueda publicar cualquiera, ya sea en una petición POST, GET, o enviándola por SSL. Así evitamos que cualquiera ejecute publica.php , sólo nosotros.

Formateando la salida en el Serial para Arduino [ parte II ]

Viernes, 26 de Agosto de 2011 Gaspar Fernández Sin comentarios

El lunes pasado empecé contando formas para formatear la salida en el Serial para Arduino y me dejé dos métodos en el tintero, relacionados con codificar a mano nuestra propia función tipo printf():

printf() usando como salida el Serial

Es fácil de programar, sólo necesitamos un rato para tenerla lista, recae por completo en la biblioteca HardwareSerial, y específicamente en el objeto Serial (aunque podremos cambiarlo cuando queramos si vamos a utilizar otro puerto serie); de primeras, si necesitamos otro puerto, tendremos que cambiar todo el código, con lo que no es demasiado reutilizable. Por otra parte, nuestro binario engorda unos 2.5Kb, lo que ya es bastante sobre todo si lo vamos a usar para depuración. Pero vamos, es sólo un experimento, el printf() nativo es mejor que este:

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
#include <stdarg.h>
int contador=1;

void setup()
{
  Serial.begin(19200);
}

static void printInteger(const int num, const unsigned base, byte signd)
{
  if (signd && num<0)    
    Serial.print(num, base);
  else
    Serial.print((unsigned)num, base);
}

static void my_vprintf(const char *fmt, va_list args)
{
  // Mientras el carácter leído no sea el terminador
  while (*fmt!=0)
    {
      if (*fmt=='%')
    {
      // Ancho, Decimales y conmutador de ancho y decimales a 0
      ++fmt;        // Incrementamos la posición
      if (*fmt=='\0')
        break;      // Fin, fmt ha terminado
      else if (*fmt=='%')
        Serial.print((*fmt));   // Copiamos el % en la cadena
      else
        {
          while ( ( (*fmt>='0') && (*fmt<='9') ) || (*fmt=='.') )
        // No aceptamos formato
        ++fmt;

          unsigned b=10,s=1// ,val=va_arg(args, int)
        ;
          switch (*fmt)
        {
        case 's':
          Serial.print((char*)va_arg(args, char*));
          break;
        case 'f':
          Serial.print((double)va_arg(args, double));
          break;
        default:
          if ( (*fmt=='i') || (*fmt=='d') )
            {
            }
          else if (*fmt=='x')
            b=16;
          else if (*fmt=='X')
            b=16;
          else if (*fmt=='o')
            b=8;
          else if (*fmt=='b')
            b=2;
          else if (*fmt=='u')
            s=0;
          else
            continue;

          printInteger(va_arg(args, int), b, s);

        }
        }
    }
      else if (*fmt=='\n')
    Serial.println();
      else if (*fmt!='\0')
    Serial.print(*fmt);

      ++fmt;
    }
}

void printSerial(const char *fmt, ...)
{
  va_list args;

  // // Obtenemos la lista de argumentos
  va_start (args, fmt );
  my_vprintf(fmt, args);
  va_end (args);
  // sp.print(tmp);  
}

void loop()
{
  printSerial("Transcurridos %d o %f segundos desde que se inicio.\n", ++contador, (float)contador/10.0);

  delay(100);
}

printf() usando como salida una cadena

Es algo así como vsnprintf(), lo sacamos todo a una cadena y luego la cadena la escribimos en el Serial que queramos. Por otra parte, aunque no podemos controlar las alineaciones, sí podemos controlar la precisión y el tamaño de los valores, lo que lo hace perfecto para depuración o escritura de informes; aunque ocupa 2Kb, puede que muchas veces prefiramos vsnprintf() de toda la vida, pero esta implementación da menos problemas utilizada dentro de clases.

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
#include <stdarg.h>
#define MAX_CADENA 128
#define MAX_BUFFER_BITS 33  // 32 + 1 (terminador)
#define MAX_FLOAT_SIZE 20
int contador=1;

void setup()
{
  Serial.begin(19200);
}

// void incdigit(int &var, int digit)
// {
//   var*=10;
//   var+=digit;
// }

static void printString (char *&out, const char *tstr, unsigned len, const int size, int width)
{
  while ( (len<size) && ( (*tstr!=0) || (width>0) ) )
    {
      *(out++)=(*tstr!='\0')?*(tstr++):' ';
      ++len;
      --width;
    }
}

static void printInteger(char *&out, const int num, const unsigned base, byte signd, int width, unsigned len, const int size, const char hexlet)
{
  char buffer[MAX_BUFFER_BITS];
  char *s=buffer+MAX_BUFFER_BITS-1;
  // byte neg=(signd)?(num<0):0;
  // unsigned wnum=(signd && num<0)?num*-1:num;
  unsigned wnum;
  if (signd && num<0)    
    wnum=num*-1;
  else
    wnum=num;

  *s='\0';

  if (wnum==0)
    *(s--)='0';
  else
    while (wnum)
      {
        s--;
        *s=wnum%base;
        if (*s>=10)
          *s+=hexlet-10;
        else
          *s+='0';

        wnum=wnum/base;
      }

  if (signd && num<0)
    *(s--)='-';

  printString(out, s, len, size, width);
}

static char* printFloat(char *out, const float num, unsigned decs)
{
  char *s=out+MAX_FLOAT_SIZE-1;
  float wnum=num;
  unsigned i;
  unsigned mult=1;

  *s='\0';
  if (num<0)
    wnum=num*-1;

  for (i=0; (i<decs && i<4); ++i)
    {
      wnum*=10;
      mult*=10;
    }
  unsigned long wun=(unsigned long)mult+wnum%mult;
  while (wun>1)
    {
      *(--s)=(wun%10)+'0';
      wun/=10;
    }

  *(--s)='.';

  i=(num<0)?num*-1:num;
  while (i)
    {
      *(--s)=(i%10)+'0';
      i/=10;
    }

  if (num<0)
    *(--s)='-';
 
  return s;
}

static void my_vsnprintf(char *out, int size, const char *fmt, va_list args)
{
  char *tmp=out;
  char buffer[MAX_BUFFER_BITS];
  char *pbuf;
  unsigned width,decs;
  byte widecs;
  int len;
  // Mientras el carácter leído no sea el terminador
  while (*fmt!=0)
    {
      if (out-tmp>=size)    // Miramos no pasarnos del tamaño
    break;

      if (*fmt=='%')
    {
      // Ancho, Decimales y conmutador de ancho y decimales a 0
      width=decs=widecs=0;   
      ++fmt;        // Incrementamos la posición
      if (*fmt=='\0')
        break;      // Fin, fmt ha terminado
      else if (*fmt=='%')
        *(out++)=(*fmt);    // Copiamos el % en la cadena
      else
        {
          while ( ( (*fmt>='0') && (*fmt<='9') ) || (*fmt=='.') )
        {
          if (*fmt=='.')
            widecs=1;
          else
            // incdigit((widecs)?decs:width, *fmt-'0');
            // Hacerlo a través de función suma casi 200bytes a nuestro binario y no es algo imprescindible.
            if (widecs)
              decs*=10+*fmt-'0';
            else
              width*=10+*fmt-'0';

          ++fmt;
        }

          unsigned b=10,s=1// ,val=va_arg(args, int)
        ;
          char let='a';
          switch (*fmt)
        {
        case 's':
          printString(out, va_arg(args, char*), (unsigned)(out-tmp), size, width);
          break;
        case 'f':
          printString(out, printFloat(buffer, va_arg(args, double), (widecs)?decs:-1), (unsigned)(out-tmp), size, width);
          break;
        default:
          if ( (*fmt=='i') || (*fmt=='d') )
            {
            }
          else if (*fmt=='x')
            b=16;
          else if (*fmt=='X')
            {
              b=16;
              let='A';
            }
          else if (*fmt=='o')
            b=8;
          else if (*fmt=='b')
            b=2;
          else if (*fmt=='u')
            s=0;
          else
            continue;

          printInteger(out, va_arg(args, int), b, s, width, (unsigned)(out-tmp), size, let);

        }
        }
    }
      else
    *(out++)=(*fmt);

      ++fmt;
    }
  *out='\0';
}

void printSerial(HardwareSerial &sp, const char *fmt, ...)
{
  char tmp[MAX_CADENA];
  va_list args;

  // // Obtenemos la lista de argumentos
  va_start (args, fmt );
  // // Escribimos en tmp, con tamaño MAX_CADENA, la cadena de formato será fmt y los
  // // argumentos args
  my_vsnprintf(tmp, MAX_CADENA, fmt, args);
  va_end (args);
  sp.print(tmp);  
}

void loop()
{
  printSerial(Serial, "Transcurridos %d o %f segundos desde que se inicio.\n", contador++, (float)contador/10.0);

  delay(100);
}

Almacenando elementos a pares en C++

Viernes, 22 de Julio de 2011 Gaspar Fernández Sin comentarios

En numerosas ocasiones necesitamos almacenar dos datos de diferente o igual tipo bajo la misma variable. Es más, en ocasiones estarán relacionados y no debemos separarlos o no tendría sentido.

La forma más inmediata es almacenar cada dato por su lado y diseñar los algoritmos de modo que siempre vayan los dos datos juntos, aunque tendremos un problema:

  • Será difícil de mantener, tenemos siempre que asegurar que los datos siempre vayan juntos.
  • Tendríamos que crear más documentación, o el que venga detrás de nosotros a hacer el programa lo pasará mal.
  • Debemos crear funciones / métodos para manejar los datos.

De todas formas, C++ , en la biblioteca estándar contienen muchas cosas ya hechas y esta es una de ellas; utilizando la clase pair podemos hacer fácilmente la asignación / comparación / lectura de pares de datos. De la siguiente manera:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

using namespace std;

int main()
{
  pair<int,string> p;

  p.first=23;
  p.second="Probandoooo...";

  cout << p.first << endl;
  cout << p.second << endl;

  p = make_pair(10, "Segunda prueba");

  cout << p.first << endl;
  cout << p.second << endl;

  return 0;
}

Fácilmente podremos crear una instancia de pair, especificando los tipos de dato a almacenar y asignarle los datos (para los ejemplos utilizaré los tipos int y string; aunque puede ser cualquier tipo de dato, clases, etc:

  • Desde el mismo constructor: pair p(999, “Dato de ejemplo”);
  • Desde los atributos first y second (como en el ejemplo)
  • Con la función make_pair(), en la que podemos introducir los objetos directamente

De esta forma, tal vez queramos introducir elementos de configuración, o asignar elementos de tipo clave=>valor, asignar un identificador a un elemento, o símplemente pasar dos datos juntos con un mismo elemento.

Pero si lo que en realidad queremos es almacenar un conjunto de datos (claves y valores) podemos utilizar map o multimap. El objetivo es introducir muchos tipos de elementos con su equivalencia (siendo ésta otro tipo de elemento distinto):

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
#include <iostream>
#include <map>

using namespace std;

int main()
{
  map <int, string> m;
  map <int, string>::iterator i;
 
  // Forma 1
  m.insert(pair<int, string>(10, "Elemento numero diez"));
  // Forma 2
  m[20]="Elemento numero veinte";
  // Forma 3
  m.insert(m.begin(), pair<int, string>(30, "Elemento numero treinta"));

  // Insertamos más elementos
  m[40]="Elemento numero cuarenta";
  m[50]="Elemento numero cincuenta";
  m[60]="Elemento numero sesenta";
  m[70]="Elemento numero ochenta";
 
  // Listamos los elementos
  for (i=m.begin(); i!=m.end(); i++)
    {
      cout << "Elemento " << i->first << " = " << i->second << endl;
    }

  cout << endl;
  // Buscamos un elemento
  i=m.find(30);
  cout << "Elemento 30 => "<<i->second<<endl;

  cout << endl;

  // Buscamos un elemento de otra forma
  cout << "Elemento 40 => "<<m[40]<<endl;

  cout << endl;

  // Cuando un elemento no existe...
  i=m.find(35);
  if (i==m.end())
    cout << "Elemento no existe" <<endl;
  else
    cout << "Elemento 35 => " << i->second <<endl;

  // Devuelve un elemento vacío (o recién construido)
  cout << "Elemento 55: "<< m[55] << endl;

  return 0;
}

Para escribir un poco menos a la hora de definir los tipos podemos utilizar typedef:

1
2
3
4
5
6
7
8
9
typedef map <int, string> mis;  // Map Int String
typedef pis <int, string> mis;  // Pair Int String

int main()
{
   mis m;
   mis::iterator m;
   m.insert(pis(10, "Cadena"));
}

O podemos utilizar el #define de toda la vida:

1
2
3
4
5
6
7
8
9
#define is int, string

int main()
{
  map<is> m;
  map<is>::iterator i;
 
  m.insert(pair<is>(10, "Elemento numero diez"));
}

Aunque tendremos que tener cuidado con lo que pongamos con #define ya que los cambios serán globales.

C.I. IX: KGPU, M$ Skype, NOSQL, Twitter VS frustración

Viernes, 13 de Mayo de 2011 Gaspar Fernández 2 comentarios

Os dejo algunos enlaces interesantes recopilados estos últimos días:

  • Speeding Up The Linux Kernel With Your GPU. Lo que leéis, hay un proyecto para acelerar el kernel con la ayuda de la GPU. Patrocinado por NVIDIA y la Universidad de Utah, puede hacer las lecturas/escrituras en sistemas de archivos cifrados 3 ó 4 veces más rápidos. En principio sólo vale para cifrado, pero bueno, sólo es cuestión de pensar qué tareas del kernel se podrán paralelizar y compensa hacer en GPU (vamos, que la transmisión de datos de y a la memoria gráfica no sea más lenta que procesar la tarea en CPU; y que la tarea sea paralelizable).
  • Microsoft adquiera Skype, ¡es hora de usar Ekiga! Ha sido la noticia de la semana, y es que no sabemos qué pasará con Skype a partir de ahora, esperemos que los clientes se sigan manteniendo y la forma de funcionar actual. Aunque es un buen momento para que los desarrolladores de proyectos libres de VoIP se pongan las pilas y mejores bastante sus proyectos, para llegar a ser verdaderos rivales para este servicio. Al final del artículo vemos una imagen que me encanta (sky .net).
  • ¿Has leído la licencia de Microsoft Windows? (Esa que todo el mundo acepta con los ojos cerrados) . De vez en cuando aparece algún artículo similar, pero es cierto que el 99% de los usuarios de Windows no lee la licencia y la acepta, aunque los fabricantes nos dan pocas opciones si se da el caso de que no aceptamos la licencia.
  • ¿Qué son las bases de datos NOSQL? Es un artículo muy interesante sobre este tipo de bases de datos, nos puede servir como un buen punto de referencia para empezar a adentrarnos en este nuevo mundo (muchos enlaces para devorar información).
  • Samsung libera código fuente del software usado en el Galaxy S II . Enhorabuena a Samsung por este movimiento, animará a muchos a tunear el sistema libremente y seguro que se promociona este modelo (y modelos futuros y derivados).
  • Las estrategias de Twitter para minimizar la frustración de los usuarios . Cualquiera diría que Twitter está reduciendo la desesperación de los usuarios, son algunos detalles subliminales curiosos.

Exportar desde MySQL a CSV

Jueves, 3 de Febrero de 2011 Gaspar Fernández 3 comentarios

Hace tiempo leí en los foros de MySQL una chuleta interesante. La copié en una página de keepnote hasta ahora. Es para hacer una petición que escriba su salida en un archivo en formato CSV directamente.

Para ello, la petición debe tener esta forma:

1
2
3
4
SELECT campos INTO OUTFILE '/ruta/archvo.csv'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'
FROM tabla;

Donde campos son los campos que vas a pedir, por ejemplo * para pedir todos; /ruta/archivo.csv es el archivo que se creará y tabla es la tabla a consultar en cuestión. Eso sí, no sólo vale para una tabla, podemos hacer peticiones todo lo complicadas que queramos y guardarlas en CSV.

Precaución, el usuario mysql (o el usuario con el que se ejecuta mysql debe tener permisos de creación en el directorio seleccionado); el archivo NO puede existir, esto es una medida de precaución para que no nos podamos cargar nada al crear archivos con MySQL; y, por último, tenemos que tener el permiso FILE en el usuario de mysql con el que estemos haciendo la consulta, para ello podemos, desde root, hacer lo siguiente:

1
GRANT FILE ON *  . *  TO 'usuario'@'host' ;

Como aclaración, debo recalcar que el permiso FILE es para usuarios de MySQL no de nuestro sistema; y el query anterior debe ser lanzado como root de MySQL.

Restablecer contraseña de root en servidor MySQL

Miércoles, 8 de Diciembre de 2010 Gaspar Fernández Sin comentarios

En ocasiones, sobre todo en nuestro servidor de pruebas (que a lo mejor puede ser
nuestro servidor de producción), podemos perder la contraseña de root de MySQL, y es un
fastidio, porque se nos puede pasar por la cabeza reconfigurar el paquete y perderlo
todo, pero son momentos en los que hay que tener paciencia y si tenemos información, no
es plan de destruir gratuitamente los datos.

Para ello, dejo unos sencillos pasos con los que restableceremos la contraseña de root
de MySQL (es necesario tener privilegios en el ordenador en el que está instalado
MySQL):

1. Primero detenemos el servicio MySQL, aquí dejo tres formas dependiendo de la
distribución:

1.1. $ service mysqld stop
1.2. $ /etc/init.d/mysqld stop
1.3. $ /etc/rc.d/mysqld stop

2. Tras ello, con privilegios de root, arrancamos MySQL de un modo especial, ignorando
las tablas de privilegios.

root $ mysqld –skip-grant-tables

Dependiendo de la versión de MySQL, puede que no nos deje arrancar el demonio como
usuario root, por lo que le daremos un nombre de usuario (este será el usuario con el
que arrancará mysql)

root $ mysqld -u mysql –skip-grant-tables &

En la mayoría de los sistemas el usuario será mysql.

3. Accedemos al servicio como superusuario (ya que no se comprueban las tablas de
privilegios, tenemos acceso). Esto lo podemos hacer como usuario normal. Accederemos
también a la base de datos llamada mysql (donde estan las tablas de privilegios)

$ mysql -u root mysql &

4. Desde mysql establecemos la nueva contraseña:

mysql> UPDATE mysql.user SET Password=PASSWORD(’nueva_contraseña’) WHERE
User=’root’;
mysql> FLUSH PRIVILEGES;

5. Salimos

mysql> quit

6. Cerramos mysqld (como root), tardará unos segundos, debemos ser pacientes

$ killall mysqld

7. Iniciamos el servidor MySQL normalmente, del mismo modo que lo paramos en el paso 1:

7.1. $ service mysqld start
7.2. $ /etc/init.d/mysqld start
7.3. $ /etc/rc.d/mysqld start

Ya debemos poder trabajar normalmente con nuestra nueva contraseña.

[ OFFTOPIC ] Acuista y su obsesión con la transferencia de datos

Jueves, 18 de Noviembre de 2010 Gaspar Fernández Sin comentarios

Veo en la web de Acuista buscando calefactores, que ahora en invierno se agradecen lo siguiente:
acuista
Y es que parece ser que en la web, cuando algo no saben cómo se mide, le ponen MB/s, y cuando algo tiene 2000MB/s de potencia… asusta.

En fin, es sólo una broma

Nos ponemos serios con PHP: Empezamos con un CRUD (la esencia no es exclusiva de PHP)

Viernes, 16 de Julio de 2010 Gaspar Fernández Sin comentarios

crudHace años tuve entre manos un gran proyecto web, en el que invertí 8 meses de mi vida y funcionó bastante bien durante un tiempo. En su desarrollo recuerdo que tuve que desarrollar cerca 50 formularios diferentes, comprobar los valores de cada uno de los campos, hacer lecturas y escrituras en base de datos con los datos obtenidos, y opcionalmente realizar alguna tarea extra una vez enviado y validado el formulario. Además, tenía que ser capaz de listar la información, modificarla y eliminarla. Por aquel entonces, aunque había partes en común y aproveché para realizar código, no lo hice de la forma más efectiva; podría haber ahorrado muchísimo trabajo.
En los últimos años, para mis proyectos web decidí fabricarme una biblioteca para hacer este tipo de tareas de forma más rápida (con menos esfuerzo, y al fin y al cabo de forma más depurada y segura). Para eso me fabriqué una biblioteca CRUD.

Es cierto que perdemos eficiencia, es más rápida por lo general una herramienta específica, que haga justo lo que se le pide en un momento dado, que una herramienta configurable y flexible (tendremos muchas más sentencias de control y a veces se ejecutarán procesos innecesarios), pero por otro lado hay que tener en cuenta que no podemos echar un mes en un proyecto que generalmente requiere unos días, y por otra parte, seguro seguro que tenemos que hacer modificaciones antes de entregarlo, y éstas no deben llevarnos otro mes más. En definitiva, nos compensa perder algo de eficiencia en favor de un tiempo de desarrollo más corto y generar un código más fácilmente mantenible.

Lo importante de un CRUD, es la posibilidad de Crear, Ver, Actualizar, Borrar (y listar información), si lo pensamos es simple, lo ideal sería tener una clase (o una función) que simplemente llamándola nos hiciera esta complicada tarea; aunque también es verdad, si lo pensamos un poco más, que podemos complicar todo esto de mil formas, y todo lo que queramos. Además, tenemos que crear una herramienta que sea lo más flexible posible para todo tipo de bases de datos y tablas, y además, sería interesante manejar los errores que puedan surgir en el proceso, para poder generar una salida acorde.

Aunque podemos encontrar bibliotecas por Internet que hacen esta tarea, yo siempre prefiero una solución DIY (Do it yourself) (Hazlo tú mismo), de esta forma, el código será 100% mío y yo controlaré el proceso por completo, además de que como materia de aprendizaje es bastante efectivo. Dejo por aquí algunas instrucciones para empezar a fabricarnos un CRUD un tanto sencillo (y que a medida que vayamos haciendo proyectos podemos ir completándolo con las necesidades que nos vayan surgiendo):

  • Listar información de base de datos: Debemos tener la posibilidad de elegir los campos a listar
  • Crear entrada: Debemos tener la posibilidad de introducir campos que concuerden con entradas en una base de datos, y también la posibilidad de generar datos automáticamente (fecha de introducción, identificadores únicos, etc):
    • Necesitaremos un generador de formularios y la posibilidad de elegir el mejor tipo de campo para cada campo de la tabla que vamos a modificar
    • Debemos tener la posibilidad de verificar, vía servidor (PHP) o cliente (Javascript), mejor hacerlo de las dos formas, que los datos estén bien formados (números máximos de caracteres, formato de introducción (por ejemplo, de una fecha, un teléfono, una dirección e-mail…)
    • Inserción en tabla, y muestra de la salida, para que el usuario vea si todo ha ido bien o mal. Sería conveniente también, que esto fuera una redirección, para que, si se refresca la página, no se reenvíen los formularios.
  • Modificar entrada:
    • Podemos aprovechar mucho código de la creación de entrada (muestra de formulario, verificación…)
    • Debemos modificar la tabla de datos, a lo mejor actualizar fechas de modificación, logs, etc
  • Eliminar entrada:
    • Idealmente, debemos presentar un diálogo tipo: “¿Está seguro?” para confirmar que no se hace por accidente, tal vez los datos no siempre interese eliminarlos, sólo hacer una marca de que no se visualicen.
  • Ver información:
    • Necesitamos hacer un sistema de visualización flexible, automático y tener la posibilidad de seleccionar los campos a mostrar (los identificadores únicos, fechas internas, IDs, no le suelen interesar al usuario), por otra parte, a veces para visualizar un dato es necesario hacer varias consultas a base de datos y tenemos que estar preparados.

Pero como dije antes, esto lo podemos complicar hasta la saciedad, aumentando el número de campos que podemos introducir (por ejemplo inserción de ficheros, Javascripts para introducir la fecha de forma fácil, o inclusión de áreas de texto HTML (como TinyMCE).

En cuanto al listado podemos implementar paginación automática (para el caso de que haya muchos elementos a listar) y ordenación de los elementos por columna (tal vez por nombre, por fecha, etc)

Por otra parte, tenemos que tener muy en cuenta el diseño, éste suele ser diferente para cada página (quitando esas páginas que parece que están hechas todas con el mismo molde), así que tenemos que hacer que nuestra salida en (x)HTML sea lo más flexible posible, que nos deje introducir código adicional, cambiar las imágenes utilizadas (iconos de insertar, eliminar…)

Y como colofón, podemos implementar soporte para relaciones, puede que echemos unos días desarrollando nuestro CRUD, pero  nos va a ahorrar mucho tiempo de desarrollo si lo utilizamos en nuestros proyectos.

Visita otras webs de la red