15/06/09

Python pour le web (avec Apache)

Python

Bon, à force de faire du PHP, je commence un peu à être lassé… Je code de plus en plus souvent des scripts en Python, notamment pour les utiliser en ligne de commande. La plus grosse difficulté, peut survenir, en l'absence d'un gestionnaire de paquets, comme c'est le cas sous Mac OS X, lors de la compilation du module mod_python.

Je m'en suis, finalement, tiré en utilisant le Python 2.5 issu de MacPorts :

./configure --with-apxs=/usr/sbin/apxs --with-python=/usr/local/bin/python2.5

Pour le httpd.conf, un simple :

LoadModule python_module libexec/apache2/mod_python.so

et, plus loin, vers la fin du fichier :

<Directory '/var/www/localhost/htdocs/'> 
	   AddHandler mod_python .py
	   PythonHandler mod_python.publisher
           PythonDebug Off
</Directory>

Ainsi qu'un changement d'utilisateur sur le dossier WebServer, utilisé, par défaut, pour notamment décompresser les .egg :

sudo chown -R root:_www '/Library/WebServer/'

Semblent largement suffisants, voir par exemple :

Commenter (0)

03/06/09

Mise à jour des ports (tâche périodique)

Terminal

Les tâches périodiques peuvent exécuter des scripts additionnels, c'est génial… parce que j'en avais un peu marre de mettre à jour "manuellement" les ports, installés avec MacPorts ; alors, je me suis dit que j'allais me faire une fonction, en Bash, à utiliser dans un weekly.local.

Comme la fonction est exécutée par l'utilisateur root, inutile de se soucier des droits… on peut, par contre, placer une simple vérification quelque part au début du weekly.local ou au sein de la fonction principale, du genre :

    [[ $UID != 0 ]] && {
	echo "Permission denied" 1>&2
	return 1
    }

Le but de la fonction est aussi d'avoir un affichage minimal, résumé, pour le fichier de log :

function update_ports() {
    [[ -x '/opt/local/bin/port' ]] && {
	export PATH="/opt/local/bin:/opt/local/sbin:$PATH"
	# http://guide.macports.org/#installing.macports.upgrade
	local dateFormat=${dateFormat:-"+%b %d %H:%M:%S"}
	local msg1=$(printf 'Updating MacPorts %s' "$(port version | awk '{print $2}')")
	local outdated=0
	local errors=0
 
	printf "%s [%s] %s\n" "$(date "$dateFormat")" $$ "$msg1"	
	port selfupdate &> /dev/null
	outdated=$(( $(port outdated | wc -l) -1 ))
	[[ "$outdated" -gt 0 ]] && {
	    errors=$(port -u upgrade outdated 2>&1 | grep -i "unable to upgrade" | wc -l)
	    [[ "$errors" -gt 0 ]] && {
		printf "%s [%s] Unable to upgrade %s ports\n" \
		    "$(date "$dateFormat")" $$ "$errors"
		port outdated
	    }
	}
    }
}

Exemple de sortie (weekly.out) :

Running /etc/weekly.local:
May 23 03:52:42 [74186] Updating MacPorts 1.710
May 23 04:13:13 [74186] Unable to upgrade 5 ports
The following installed ports are outdated:
fontconfig                     2.6.0_1 < 2.6.0_2         
MPlayer                        1.0rc2_3 < 1.0rc2_5       
pango                          1.22.4_3 < 1.24.2_0       

-- End of weekly output --

Commenter (0)

03/06/09

Installation de mpd et ncmpc

Terminal

Dans l'idée de profiter de son Terminal à 100%, il y a aussi les lecteurs de musique… et ce serait dommage de se priver de l'efficacité de MacPorts, même, si comme on le verra, tout n'est pas forcément disponible, et qu'il arrive, encore quelques fois qu'il soit nécessaire de compiler à la main :wink:.

Après, avoir installé mocp (capture), je cherchais quelque chose du même genre, pour varier les plaisirs… et j'ai vu que ncmpc est une interface (capture) en console pour mpd.

sudo port install mpd +aac+audiofile+ffmpeg+flac+macosx+mp3+mpc+vorbis
sudo launchctl load -w /Library/LaunchDaemons/org.macports.mpd.plist
mkdir -p ~/.mpd/playlists
touch ~/.mpdconf
open -t !*

Fichier de configuration minimal :

music_directory         "/Volumes/WD800BB/Music/"
playlist_directory      "~/.mpd/playlists"
db_file                 "~/.mpd/tag_cache"
log_file                "~/.mpd/mpd.log"
error_file              "~/.mpd/errors.log"
pid_file                "~/.mpd/pid"
user                    "walter"

Il faudra éviter de lancer mpd en démon, c'est ballot, à cause d'un bug, alors pour le tester tout de suite après son installation, il faudra utiliser l'option --no-daemon, ensuite si le plist de MacPorts est chargé par launchd ça sera automatique.

Pour installer ncmpc comme il n'est pas disponible dans les ports de MacPorts, il faudra le télécharger sur le site officiel : A ncurses MPD client - Download, ensuite, rien de bien compliqué :

./configure
make CFLAGS=-fnested-functions
sudo make install

Voilà, c'est, je pense, le minimum pour pouvoir utiliser ncmpc ; et c'est à peine plus compliqué que pour mocp… côté consommation de ressource, ça n'a pas grand chose à voir avec iTunes :tongue:.

Une fonction qui semble manquer à ncmpc par rapport à mocp c'est l'ajout récursif de répertoires, non en fait il n'y a pas de raccourci spécial pour çà : c'est comme l'ajout d'un fichier, barre d'espace. Par contre ncmpc est localisé en français, alors que mocp ne l'est pas, du moins chez moi.

Commenter (0)

27/05/09

Installer eAccelerator 0.9.5.3

Apache PHP

Le choix des caches opcode et des "accélérateurs PHP" est relativement vaste et, qui plus est, soumis à controverses avec pléthore de benchmarks et de tests. Finalement, mon choix s'est porté sur eAccelerator ; bien qu'ayant tenté, avant, une installation de XCache, qui n'était malheureusement pas compatible avec ma configuration de PHP.

Je me suis fait, pour l'occasion et parce que je n'avais pas trop envie de m'y reprendre à plusieurs fois, un petit script d'installation, basé sur la documentation… qui plus est, le script sera facilement modifiable pour effectuer une mise à jour ou une réinstallation.

#!/usr/bin/env bash
 
file_web="http://bart.eaccelerator.net/source/0.9.5.3/eaccelerator-0.9.5.3.zip"
export PHP_PREFIX="/usr/local/php5"
 
rm -rf ./src &> /dev/null
PATH="${PHP_PREFIX}:$PATH"
mkdir src && {
    cd ./src
    printf "downloading %s\n" $(basename "${file_web}")
    curl "${file_web}" -#o 'eaccelerator.zip'
    unzip 'eaccelerator.zip' > /dev/null && {
	cd ./eaccelerator-*
	$PHP_PREFIX/bin/phpize && {
	    ./configure --with-php-config="${PHP_PREFIX}/bin/php-config" \
		--enable-eaccelerator=shared && {
		make &> /dev/null && {
		    make test
		    sudo make install
		}
	    }
	}
    }
}

Bon, l'utilisation d'eAccelerator ne remplace pas la poudre verte et, encore moins, toute autre forme d'optimisation, par exemple la mise en cache server-side. Mais, apparemment, même sans écrire du code spécifique, ça ne semble pas relever uniquement du tuning :wink:.

Commenter (0)

27/05/09

Améliorations du blog (Saison 3)

PHP

La pagination est, enfin, arrivée… sur la page d'accueil du blog, mais aussi sur les résultats de recherche ; comme, par exemple, avec une recherche : Améliorations du blog.

La plupart des scripts AJAX ont été améliorés sur la rapidité d'exécution, server-side, particulièrement celui affichant les favicons des liens externes, avec l'utilisation de tableaux associatifs sérialisés utilisés en guise de "bases de données rudimentaires" réduisant fortement la fréquence de certaines opérations lourdes. Les JavaScript ont été réunis en un seul script "minifié" généré dynamiquement côté serveur (gain de poids et de quelques hits).

Un système de cache a été mis en place ; conduisant a un gain de performances notable, particulièrement lorsque la machine, hébergeant le blog, est soumise à une charge élevée. Chaque billet et chaque commentaire est mis en cache, puis rafraîchit en cas de modification ; les flux RSS sont quant à eux mis en cache pour une durée correspondant au ttl.

La configuration du blog est maintenant gérée par une property list (.plist), c'est d'ailleurs dommage que PHP ne gère pas ce format de façon native :sad:.

Reste encore à parfaire la gestion de l'UTF-8, là aussi, avec PHP c'est loin d'être entièrement natif :dizzy:… À moins, peut-être, d'utiliser les fonctions sur les chaînes de caractères multi-octets ?

Commenter (0)

26/04/09

French Language Pack (fr-FR) 3.5b5

Firefox

Pack de langue français complet pour Firefox 3

Avec un peu de retard… bien que le XPI soit prêt depuis 2 jours :neutral:, voici le French Language Pack (fr-FR) 3.5b5pre.

Modification du fr.jar et nouveaux dictionnaires : hunspell_fr-classique_3-2.

Ce nouveau pack est censé fonctionner avec Firefox 3.1b3, 3.5b4 et 3.5b5, il n'a cependant été testé qu'avec Firefox 3.5b5pre.

Pour retrouver toutes les versions des packs de langue français complet pour Firefox.

Version 3.5b5

Fonctionne avec Firefox 3.1b3, 3.5b4 et 3.5b5
Télécharger : langpack-fr-Fx3.5b5.xpi
MD5 (langpack-fr-Fx3.5b5) = 0c31ce0bd63b5b5b1031514b24e2bfd0

Commenter (0)

26/04/09

Héritage et PEAR::Cache_Lite

PHP

Je voulais utiliser une bibliothèque PEAR, qu'on ne présente plus, PEAR::Cache_Lite ; seulement, il y a, à mon sens, une bizarrerie, du moins dans la version dont je disposais et qu'il faut que je mette à jour, c'est que la méthode lastModified() ne semble pas opérationnelle.

Je m'attendais à un comportement similaire à celui des méthodes save() et remove(). Parce que, en effet, je souhaitais gérer mes fichiers de cache "manuellement", en me basant sur la comparaison de la date de modification de divers fichiers et dossiers, au lieu de la méthode généralement préconisée qui est de n'utiliser que la lifeTime des options de Cache_Lite. Malheureusement, la méthode lastModified() n'a jamais rien retourné chez moi :

    function lastModified() 
    {
        return @filemtime($this->_file);
    }

Bon, qu'à cela ne tienne, j'avais aussi un petit souci, avec la méthode raiseError(), qui est censée faire un include sur un fichier que je n'utilise pas :

    function raiseError($msg, $code)
    {
        include_once('PEAR.php');
        return PEAR::raiseError($msg, $code, $this->_pearErrorMode);
    }

Je n'avais plus qu'à hériter, ce qui me permettra de mettre à jour mon paquet Cache_Lite sans me soucier des modifications que j'y aurais apporté.

extends.Lite.php

<?php
  /**
   * extends.Lite.php
   * Time-stamp: <2009-04-26 13:50:09 (blogosx.homeunix.org)> 
   * @extends Cache_Lite
   */
 
class MyCacheLite extends Cache_Lite {
 
    /**
     * Return the cache last modification of a cache file
     *
     * @param string $id cache id
     * @param string $group name of the cache group
     * @return int last modification time
     * @access public
     */
    function lastModified($id, $group='default') {
        $this->_setFileName($id, $group);
        return (int)@filemtime($this->_file);
    }
 
    /**
     * Trigger a PEAR error
     *
     * @param string $msg error message
     * @param int $code error code
     * @access public
     */
    function raiseError($msg, $code) {
	throw new Exception($msg, $code);
    }
  }
?>

Ainsi, la méthode lastModified() obtient un comportement similaire à celui de la méthode remove() et leurs signatures sont identiques.

Et après vérification, Lite.php,v 1.53 2009/01/13 nécessiterait les mêmes modifications pour mettre en "cache PHP" mon main.min.js, compressé avec jsmin-php ; mais l'héritage me permet d'envisager les mises à jour des dépendances avec sérénité :smile:.

Commenter (0)

15/04/09

Lisp Metadata Importer 0.0.2

Finder

Comme je me suis récemment mis à la programmation en Lisp, après avoir installé clisp à l'aide de MacPorts ; après tout çà, et la complète réorganisation de mon .emacs.el, je me suis dit qu'une indexation dans Spotlight des fichiers lisp, et surtout de leur contenu, pourrait être un plus. Alors j'ai cherché un plugin pour Spotlight et j'ai trouvé : Lisp Metadata Importer 0.0.2. Comme il n'était disponible qu'en version PPC, mais qu'il était accompagné de ses sources, je l'ai recompilé en version UB.

Lisp Metadata Importer 0.0.2 (UB - OS X 10.5)
MD5 (Lisp Metadata Importer 0.0.2.tar.gz) = 4cbfe62953dec9b486bead4f2a2f6216

Malheureusement, ne disposant pas du SDK Mac OS X 10.4, la version compilée ici ne devrait être compatible qu'avec Mac OS X 10.5 et supérieur. Sinon, après avoir compilé le plugin, dont le plist a été modifié pour supporter l'extension el, et avoir forcé l'indexation :

mdimport -d1 ~/.emacs.d/*

Ça fonctionne assez bien, même si l'indexation m'a parue assez longue avant que l'ensemble du contenu de tous les fichiers ne soit indexé : capture d'écran dans le Finder.

Alors à défaut, une solution plus rapide à mes "problèmes" de recherche, pourrait être grep qui peut s'avérer très efficient, et en couleur :smile:.

Commenter (0)

02/04/09

French Language Pack (fr-FR) 3.5b4

Firefox

Pack de langue français complet pour Firefox 3

La précédente version du langpack était uniquement compatible avec Firefox 3.1b3, ceci était dû à un souci dans le chrome.manifest de l'extension qui empêchait sa compatibilité avec Firefox 3.5b4pre, ce souci a été fixé, et ainsi cette nouvelle version du langpack assure-t-elle une meilleure compatibilité.

Version 3.5b4

Fonctionne avec Firefox 3.1b3 et Firefox 3.5b4
Télécharger : langpack-fr-Fx3.5b4.xpi
MD5 (langpack-fr-Fx3.5b4) = d3e32831a933abf8bee6b578c9aa00da

Commenter (0)

02/04/09

Nettoyer l'User-Agent avec userChrome.js

Firefox

Un détail qui m'agace c'est la tendance de certaines extensions, mais aussi certaines builds, d'ajouter des éléments, dont la pertinence n'est pas toujours très bien définie, à la chaîne user agent de Firefox. Évidemment, on pourrait utiliser le user.js pour régler ceci… en théorie, et en théorie seulement. En pratique, il s'avère que le user.js semblant inefficace, j'ai souvent été obligé de passer par le about:config. Aussi, c'est pourquoi je me suis décidé à écrire un petit script JavaScript qui soit relativement facile à modifier et utiliser, pour réinitialiser la clef general.useragent.extra.firefox et facilement effacer n'importe quelle clef de type general.useragent.extra.

CleanMyUserAgent.js

/**
 * Time-stamp: <2009-04-04 12:40:31 (blogosx.homeunix.org)>
 * @name           CleanMyUserAgent.js
 * @namespace      org.blogosx.cleanmyuseragent
 * @version        1.0.2
 * @homepage       http://blogosx.homeunix.org/
 * @description	   Clean User-Agent in Mozilla Firefox.
 */
 
(function() {
    function main() {
	var UACleaner = new UserAgentCleaner();
	// UACleaner.appName = String('Minefield');
	UACleaner.main();
    }
 
    function Console() {
	this.ConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
	    getService(Components.interfaces.nsIConsoleService);
 
	Console.prototype.log = function(message) {
	    this.ConsoleService.logStringMessage(message);
	};
    }
 
    function Prefs() {
	this.PrefServ = Components.classes["@mozilla.org/preferences-service;1"]
	    .getService(Components.interfaces.nsIPrefService);
 
	Prefs.prototype.setCharPref = function(key, value) {
	    this.PrefServ.setCharPref(key, value);
	};
 
	Prefs.prototype.getCharPref = function(key) {
	    try {
		return this.PrefServ.getCharPref(key);
	    } catch(e) {
		return false;
	    }
	};
 
	Prefs.prototype.getChildList = function(domain) {
	    var ConServ = new Console();
	    var rChilds = [];
 
	    try {
		var Branch = this.PrefServ.getBranch(domain);
		rChilds = Branch.getChildList("", {});
 
		ConServ.log('Extras: ' + rChilds.join(', '));
	    } catch(e) {
		ConServ.log('Error: ' + e);
	    }
 
	    return rChilds;
	};
 
    }
 
    function UserAgentCleaner() {
	this.AppInfos = Components.classes["@mozilla.org/xre/app-info;1"]
	    .getService(Components.interfaces.nsIXULAppInfo);
	this.PrefServ = new Prefs();
	this.Console  = new Console();
 
	this.appName = String(this.AppInfos.name);	
	this.extraFirefox = String(this.appName + '/' + this.AppInfos.version);
 
	UserAgentCleaner.prototype.initExtraName = function () {
	    this.extraFirefox = String(this.appName + '/' + this.AppInfos.version);
	};
 
	UserAgentCleaner.prototype.resetExtraName = function () {
	    var id = 'general.useragent.extra.' + this.AppInfos.name.toLowerCase();
	    this.initExtraName();	
	    if (this.PrefServ.getCharPref(id) != this.extraFirefox) {
		this.PrefServ.setCharPref(id, this.extraFirefox);
		this.Console.log("`" + id + "' has been reset.");
	    }
	};
 
	UserAgentCleaner.prototype.unsetExtra = function(last_id) {
	    try {
		if (last_id != this.AppInfos.name.toLowerCase()) {
		    var id = 'general.useragent.extra.' + last_id;
		    if (this.PrefServ.getCharPref(id)) {
			this.PrefServ.setCharPref(id, '');
			this.Console.log("`" + id + "' has been unset.");
			return true;
		    } else {
			this.Console.log("`" + id +  "' can not be altered using this method.");
		    }
		}
	    } catch(e) {
		this.Console.log('Error: ' + e);
	    }
	    return false;
	};
 
	UserAgentCleaner.prototype.getExtras = function() {
	    return this.PrefServ.getChildList('general.useragent.extra.');
	};
 
	UserAgentCleaner.prototype.unsetExtras = function() {
	    var extras = this.getExtras();
 
	    for (i=0; i< extras.length; i++) {
		if (extras[i] != this.AppInfos.name.toLowerCase()) {
		    this.unsetExtra(extras[i]);
		}
	    }
	};
 
	UserAgentCleaner.prototype.main = function() {	    
	    this.resetExtraName();
	    this.unsetExtras();
	};
    }
 
    main();
})(); 
 
/**
 * Local Variables: 
 * mode: JavaScript 
 * coding: utf-8 
 * End:
 */

Explications

Malheureusement, pour des raisons de sécurité, en l'état, cette fonction n'est pas utilisable avec Greasemonkey, si bien que je ne le propose pas en UserScript, pour éviter les soucis. J'utilise, donc, cette fonction avec userChromeJS qui permet d'avoir un comportement, vis-à-vis de la sécurité, similaire à celui d'une extension.

La fonction va réinitialiser la clef general.useragent.extra.firefox avec pour valeur la propriété appName et le numéro de version de Firefox, avant d'essayer de donner comme valeur une chaîne nulle à toutes les clefs general.useragent.extra.* existantes, si nécessaire ; sauf, évidemment, pour la clef general.useragent.extra.firefox.

Et c'est là que la supériorité par rapport au user.js est flagrante…
tout est entièrement automatisé ! :happy:

Un User-Agent tout propre !

Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; fr-FR; rv:1.9.1b4pre) Gecko/20090401 Minefield/3.5b4pre

Comparez avec :

Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.6 MEGAUPLOAD 1.0 (.NET CLR 3.5.30729)
Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.5, Ant.com Toolbar 1.3 (.NET CLR 3.5.30729)
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; FunWebProducts; Every Toolbar; .NET CLR 1.1.4322)
Mozilla/5.0 (Macintosh; U; Intel Mac OS X; fr; rv:1.8.1.4pre) Gecko/20070426 Firefox/2.0.0.4pre (Mac Community Build, ElFurbe)
Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1.6) Gecko/20070725 ImageShackToolbar/4.2.1 Firefox/2.0.0.6

Utilisation

Pour son utilisation, il suffit d'ajouter, par exemple, dans le userChrome.js :

if (location == "chrome://browser/content/browser.xul") {
    userChrome.import("/ucjs/CleanMyUserAgent.js", "UChrm");
}

De part l'utilisation dans l'userChrome.js, le script sera chargé et exécuté au démarrage du navigateur, cette modification sera considérée comme étant une modification effectuée par l'utilisateur, avec toutes les conséquences que ça suppose. C'était aussi, et surtout, une occasion d'utiliser les Components.classes de Firefox, et de m'exercer à la POO en JavaScript ; ça doit sûrement être perfectible :wink:.

Commenter (0)