Alexandre DE DOMMELIN weblog

I would love to change the world but they won’t give me the source code

  • Archives
  • XML feed

Notifications depuis irssi distant grace a libnotify »

Voici une solution pour afficher des notifications locales lorsque de la reception de messages dans votre irssi distant (sans X forwarding)...

Il existe plusieurs solutions pour afficher des notifications locales lorsque vous recevez des messages sur votre irssi distant.
Ne souhaitant pas mettre en place une solution necessitant l'installation de paquets relatifs à X sur mon serveur, je me suis tourné vers une solution utilisant un petit script perl int&eagré à irssi et d'un autre petit script sur le poste client qui se chargera d'afficher les notifications sur le bureau.

Configuration côté serveur

Copiez/collez le code ci-dessous dans un fichier qui sera placé dans le repertoire ~/.irssi/scripts/autorun/

use strict;
use Irssi;

sub priv_msg {
        my ($server,$msg,$nick,$address,$target) = @_;
        filewrite($nick." " .$msg );
}

sub hilight {
    my ($dest, $text, $stripped) = @_;
    if ($dest->{level} & MSGLEVEL_HILIGHT) {
        filewrite($dest->{target}. " " .$stripped );
    }
}

sub filewrite {
        my ($text) = @_;
        open(FILE,">>$ENV{HOME}/.irssi/notifications_log");
        print FILE $text . "\n";
        close (FILE);
}

Irssi::signal_add_last("message private", "priv_msg");
Irssi::signal_add_last("print text", "hilight");

Ce bout de code logguera vos messages privés dans le fichier ~/.irssi/notifications_log
C'est fini pour le serveur, passons à la suite.

Configuration côté client

Tout d'abord, commencez par installer le paquet libnotify-bin (en tout cas il s'appelle comme ca sous debian, il fournit le binaire notify-send) Copiez/collez le code ci-dessous dans un fichier que vous placerez dans votre repertoire de scripts persos favoris (~/bin dans mon cas)

#!/bin/sh
ssh user@host.tld tail -F -n1 ~/.irssi/notifications_log | while read heading message
do notify-send -i gtk-dialog-info -t 300000 -- "${heading}" "${message}"
done


Pensez à mettre votre login / adresse serveur à la place de "user@host.tld", le bit d'execution sur le script, puis rajoutez une ligne dans votre ~/.xinitrc pour qu'il soit lancé automatiquement lors de votre connexion.

Conclusion

Relancez votre irssi, lancez le script sur votre client et attendez qu'un ami vous parle (ou alors envoyez-vous vous meme un message avec un autre client si vous n'avez pas d'amis).
Dernier point, il est quand meme largement conseillé d'avoir votre clef ssh de posée sur votre serveur et de prevoir un mécanisme de saisie de votre passphrase AVANT d'appeler votre script client.
09-06-2009

SQL : Selectionner le premier/dernier/plus grand resultat de chaque groupe »

Il existe des problèmes courants en SQL : trouver le log le plus récent pour chaque programme, l'article le plus populaire par catégorie, le meilleur score pour chaque joueur ... nous allons tenter de faire le tour de ces questions ainsi que de la possibilté d'extraire les N premiers resultats ...

Afin d'illuster mes propos, je vais utiliser un exemple avec une table "fruits" contenant quelques enregistrements :

+--------+------------+-------+
| type   | variete    | prix  |
+--------+------------+-------+
| pomme  | gala       |  2.79 | 
| pomme  | fuji       |  0.24 | 
| pomme  | Borowitsky |  2.87 | 
| orange | valencia   |  3.59 | 
| orange | navel      |  9.36 | 
| poire  | bradford   |  6.05 | 
| poire  | bartlett   |  2.14 | 
| cerise | bing       |  2.55 | 
| cerise | chelan     |  6.33 | 
+--------+------------+-------+



Sélectionner une entrée extreme dans chaque groupe


Il est courant de vouloir selectionner dans une table le log le plus récent pour chaque programme, ou quelque chose dans le style. Si l'on essaie d'appliquer ce type de problème à notre table de fruits, nous pourrions par exemple vouloir trouver, pour chaque type la variété la moins chère.
Voici ce que nous souhaitons obtenir :

+--------+----------+-------+
| type   | variete  | prix  |
+--------+----------+-------+
| pomme  | fuji     |  0.24 | 
| orange | valencia |  3.59 | 
| poire  | bartlett |  2.14 | 
| cerise | bing     |  2.55 | 
+--------+----------+-------+ 

Il existe quelques solutions à ce problème. Toutes fonctionnent en 2 temps : trouver le prix desiré, puis selectionner le reste des données en ce basant sur ce résultat.
Une solution courante est appellé "auto-jointure". L'étape 1 est de grouper les fruits par type, et d'en choisir le prix minimum :

SELECT type, MIN(prix ) AS minprix 
FROM fruits
GROUP BY type;

+--------+----------+
| type   | minprix  |
+--------+----------+
| pomme  |     0.24 | 
| cerise |     2.55 | 
| orange |     3.59 | 
| poire  |     2.14 | 
+--------+----------+

L'étape 2 est de selectionner le reste des lignes en faisant une auto-jointure sur la table fruits. Comme notre premiere requete est groupé il est nécessaire de l'intégrer dans une sous-requete afin de faire notre jointure sur la table de base non-groupée :

SELECT f.type, f.variete, f.prix 
FROM (
   SELECT type, MIN(prix ) AS minprix 
   FROM fruits GROUP BY type
) as x INNER JOIN fruits AS f ON f.type = x.type AND f.prix  = x.minprix ;

+--------+----------+-------+
| type   | variete  | prix  |
+--------+----------+-------+
| pomme  | fuji     |  0.24 | 
| cerise | bing     |  2.55 | 
| orange | valencia |  3.59 | 
| poire  | bartlett |  2.14 | 
+--------+----------+-------+

Une autre solution, plus lisible mais potentiellement plus lente est d'utiliser une requete imbriquée :

SELECT type, variete, prix 
FROM fruits
WHERE prix  = (SELECT MIN(prix ) FROM fruits AS f WHERE f.type = fruits.type);

+--------+----------+-------+
| type   | variete  | prix  |
+--------+----------+-------+
| pomme  | fuji     |  0.24 | 
| orange | valencia |  3.59 | 
| poire  | bartlett |  2.14 | 
| cerise | bing     |  2.55 | 
+--------+----------+-------+



Selectionner les N entrées extremes de chaque groupe


Ce probléme est plus compliqué que le premier à résoudre. Sortir un seul enregistrement est relativement simple grace aux fonctions MIN(), MAX() ... mais il est impossible de les utiliser dans notre cas dans le sens ou ces fonctions ne peuvent renvoyer qu'une seule valeur.
Disons que nous souhaitons trouver les 2 variétés les moins chès de chaque type :

SELECT type, variete, prix 
FROM fruits
WHERE (
   SELECT COUNT(*) FROM fruits AS f
   WHERE f.type = fruits.type AND f.prix  < fruits.prix 
) <= 2;

+--------+----------+-------+
| type   | variete  | prix  |
+--------+----------+-------+
| pomme  | gala     |  2.79 | 
| pomme  | fuji     |  0.24 | 
| orange | valencia |  3.59 | 
| orange | navel    |  9.36 | 
| poire  | bradford |  6.05 | 
| poire  | bartlett |  2.14 | 
| cerise | bing     |  2.55 | 
| cerise | chelan   |  6.33 | 
+--------+----------+-------+

Cette requete peut être expliquée comme cela : "Selectionner la variété de chaque type ou la variété n'est pas plus chère que la 2ème moins chème de chaque type" (ouf !).
Cette manière de faire est relativement propre dans le sens ou il est possible de faire varier le nombre d'entrées a selectionner facilement. En revanche au niveau performances, ce n'est pas vraiment le top, l'utilisation d'algo quadratique devient assez lourd lorsque le nombre d'enregistrements devient important, surtout si les index ne sont pas ou mal définis.Existe-t-il une ou plusieurs solutions plus optimisées ?



L'UNION fait la force


Si votre table dispose d'un index (type,prix ) et qu'il y a beaucoup plus d'enregistrements à eliminer qu'à recuperer pour chaque groupe, une methode plus efficace (surtout sur MySQL) est de splitter les differentes requetes, de poser une limite sur chacunes d'entres elles puis d'utiliser UNION pour les remettre ensemble. Voici un exemple :

(SELECT * FROM fruits WHERE type = 'pomme' ORDER BY prix  LIMIT 2)
UNION ALL
(SELECT * FROM fruits WHERE type = 'orange' ORDER BY prix  LIMIT 2)
UNION ALL
(SELECT * FROM fruits WHERE type = 'poire' ORDER BY prix  LIMIT 2)
UNION ALL
(SELECT * FROM fruits WHERE type = 'cerise' ORDER BY prix  LIMIT 2)


Paul Zaitev a fait une description approfondie de cette technique, je vous conseille de le lire si vous voulez en savoir plus sur cette dernière.



Petite solution spécifique à MySQL utilisant des variables

Une autre solution, spécifique à MySQL, dans le cas ou vous souhaitez récuperer un plus grand nombre d'enregistrements, est de passer par des variables :

set @num := 0, @type := '';

SELECT type, variete, prix 
FROM (
   SELECT type, variete, prix ,
      @num := if(@type = type, @num + 1, 1) AS row_number,
      @type := type AS dummy
  FROM fruits
  ORDER BY type, prix 
) AS x WHERE x.row_number <= 2;

Cette requete s'effectue au final en 2 passes, la sous-requetes construisant implicitement une table remplie avec les données sur lesquelles on applique tour à tour la clause WHERE.



Conclusion


Nous avons fait un tour rapide de quelques solutions au problème qui est de pouvoir recuperer "les valeurs extremes de chaque groupe" et également de pouvoir selectionner les N extremes resultats de chaque groupe.
La plupart des techniques sont applicables sur divers SGBD, meme si la derniere solution est MySQL-Specifique. Cet article est inspiré de techniques décrites par B. Schwartz avec son autorisation expresse.
11-04-2009

Customizer les pages 404 sous lighttpd »

Voici comment personnaliser les pages d'erreur sous lighttpd

Après etre tombé sur un article traitant des nouvelles fonctionnalités de la dernière version de la Google Toolbar, j'ai pris conscience de la nécéssité de mettre en place des pages 404 personnalisées.
En effet toutes les pages pesant moins de 512 octets seront remplacées par une page Google contenant un champ de recherche qui risque de rediriger votre traffic sur un autre site.

Assez blablaté passons à la customization de vos pages, la procédure est tres simple :

Editer le fichier de conf de lighttpd :
# vim /etc/lighttpd/lighttpd.conf
Rajoutez la ligne suivante : server.error-handler-404 = “/404-handler.html”
NB : il est bien sur possible de remplacer la page HTML par une page PHP, Perl ...

Il ne reste plus qu'a créer une page 404-handler.html a la racine du serveur puis de relancer lighttpd # /etc/init.d/lighttpd restart

10-04-2009

Desactiver console.log quand firebug n'est pas disponible »

Firebug propose une fonctionnalité utile pour debugger du JS : console.log()
Seul problème, si un appel reste dans le code et que firebug n'est pas installé le JS remonte une erreur et stoppe son execution, voila une solution...

Je trouve la fonction console.log() très pratique pour debugger sans avoir recours à des alert() à tout va. Le problème c'est qu'il est très facile d'oublier un appel à cette fonction quelque part dans le code, résultat tous les browsers ne disposant pas de firebug installé planteront lors de l'execution. Voilà une solution permettant de bypasser ce problème :

<script type="text/javascript">
        if (typeof console != "object") {
                var console = {
                        'log':function() {}
                };
        }
</script>


Bien entendu, c'est une solution de contournement et il est preferable de supprimer tous les appels à console.log() une fois le dev terminé
09-04-2009

Email validation using Regular Expression »

Working on various web projects, there's a very well known problem : find a good regular expression (regexp) to check the validity of user submitted email addresses. Here's my solution ...

Working on various web projects, there's a very well known problem : find a good regular expression (regexp) to check the validity of user submitted email addresses.
This website has compiled various regular expressions which try to resolve this problem. His idea is great, using a set of valid/invalid emails, and a simple unit test, he can provide a good comparison of some of the most used regexps.

His philosophy is great : "It's my philosophy that it's better to accept a few invalid addresses than reject any valid ones, so I'm shooting for 0 false-positives and as few false-negatives as possible."
But I've noticed 2 problems :

  • His "best" regexp doesn't work in javascript (JS doesn't support advanced features like negative lookbehind ...)
  • The method used to validate IP addresses is not correct (doesn't take care of 0-255 range)
So i've decided to improve an existant one, adding an other test criteria : also check the "real" validity of the IP address. The following work is based on Warren Gaebel's regex, including improvement proposed by G. Arluison.

Here's my solution :/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9]([-a-z0-9_]?[a-z0-9])*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z]{2})|([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})(\.([1]?\d{1,2}|2[0-4]{1}\d{1}|25[0-5]{1})){3})(:[0-9]{1,5})?$/i

Here are the results :

Should be Valid:

l3tt3rsAndNumb3rs@domain.com Valid
has-dash@domain.com Valid
hasApostrophe.o'leary@domain.org Valid
uncommonTLD@domain.museum Valid
uncommonTLD@domain.travel Valid
uncommonTLD@domain.mobi Valid
countryCodeTLD@domain.uk Valid
lettersInDomain@911.com Valid
underscore_inLocal@domain.net Valid
IPInsteadOfDomain@127.0.0.1 Valid
IPAndPort@127.0.0.1:25 Valid
subdomain@sub.domain.com Valid
local@dash-inDomain.com Valid
dot.inLocal@foo.com Valid
a@singleLetterLocal.org Valid
singleLetterDomain@x.org Valid
&*=?^+{}'~@validCharsInLocal.net Valid

 

Should be NOT Valid :

missingDomain@.com Not Valid
@missingLocal.org Not Valid
missingatSign.net Not Valid
missingDot@com Not Valid
two@@signs.com Not Valid
colonButNoPort@127.0.0.1: Not Valid
  Not Valid
WrongIpAddr@256.10.12.154 Not Valid
someone-else@127.0.0.1.26 Not Valid
.localStartsWithDot@domain.com Not Valid
localEndsWithDot.@domain.com Not Valid
two..consecutiveDots@domain.com Not Valid
domainStartsWithDash@-domain.com Not Valid
domainEndsWithDash@domain-.com Not Valid
TLDDoesntExist@domain.moc Not Valid
numbersInTLD@domain.c0m Not Valid
missingTLD@domain. Not Valid
! "#$%(),/;<>[]`|@invalidCharsInLocal.org Not Valid
invalidCharsInDomain@! "#$%(),/;<>_[]`|.org Not Valid
local@SecondLevelDomainNamesAreInvalidIfTheyAreLongerThan64Charactersss.org Valid

Results :
Valid : 18/18
Invalid : 19/20

I'm very happy with this solution for a lot of points : it is a portable one (usable on client-side via JS, or server side with languages providing PCRE functions), it does deep TLD and IP address check. If you want to add an other TLD (more than 2 chars long), just append it to the list.

I'm working on a solution to check the length of the address. Based on the RFC, an email should be composed like that :

  • Local part : max 64 chars
  • Domain part : max 255 chars
In sum, an email address can be 320 characters long at most.

If you want to test it, here comes a small PHP script using the preg_match() function : test_email.php

To be continued ... :-)
28-03-2009

About ...

CC by-nc-sa

photo
Mon CV sur LinkedIn
Ma page sur Debian Q.A
Del.icio.us Bookmarks