Overblog Suivre ce blog
Editer l'article Administration Créer mon blog
8 septembre 2013 7 08 /09 /septembre /2013 17:45

Une des nouvelles fonctionnalités que j'ai ajoutées à Rossignol dans la version 0.2 est la cohabitation avec Firefox. Il ne s'agit bien évidemment pas d'une version de Rossignol réécrite en JavaScript et XUL, mais d'un ensemble de fonctionnalités ajoutées permettant d'interagir sans peine avec le navigateur.

 

État des lieux des flux avec Firefox

Lorsque l'on affiche un flux RSS ou Atom avec Firefox, celui-ci nous propose de s'y abonner, via son système de marque-pages dynamiques, mais propose aussi d'utiliser une application tierce pour cela. Il suffit alors de parcourir l'arborescence du disque et d'indiquer à Firefox l'exécutable à utiliser.

 

screen.png

 

Lorsque l'on s'abonne à un flux de cette manière, Firefox se contente de lancer l'exécutable sélectionné et de lui passer comme paramètre l'URL du flux avec le préfixe d'URI feed :

 

 rossignol feed://www.monsite.com/flux.xml 

 

À charge alors à l'application de s'abonner le flux passé en paramètre. Jusqu'ici, tout va bien.

Ce qui se complique un petit peu, c'est ce qui se passe lorsque l'application en question est déjà en cours d'exécution. Il faut bien comprendre qu'en l'état actuel des choses, nous risquons d'avoir une instance de l'application avec N flux et une autre avec N+1 flux. Tout ce petit monde doit donc communiquer pour synchroniser sa configuration afin que :

  • L'instance 1 n'écrase pas la configuration de l'instance 2 avec la sienne (qui compte un flux de moins).
  • L'utilisateur ait toujours à l'écran quelque chose de cohérent par rapport à ses actions, et non pas un état antérieur celles-ci.

 

La solution retenue

Rossignol ne sauvegarde sa configuration qu'à la fermeture de l'application. Le cas n°1 pourrait donc se produire si l'utilisateur fermait la première instance de Rossignol après la deuxième. J'ai donc choisi volontairement d'interdire l'ouverture de plusieurs instances de Rossignol par un même utilisateur. Au démarrage, on vérifie que l'on est la seule instance en cours d'exécution, si c'est le cas, on démarre normalement. Sinon, on se connecte à l'autre instance par un mécanisme de communication interprocessus et on lui envoie nos arguments de ligne de commande avant de s'arrêter promptement.

 

Comment tester que la présence d'une autre instance de l'application ?

Sous Windows, il existe plusieurs possibilités, mais la solution recommandée par Microsoft est d'utiliser un Mutex nommé. Le nom du mutex doit être préfixé par « Global\ » pour indiquer au système que l'on veut un mutex visible pour tout le système.

 
 auto handle = CreateNamedMutexW(null, true, name);

if (handle is null)
{
// erreur
}
else if (GetLastError() == ERROR_ALREADY_EXISTS)
{
// une autre instance est en cours d'exécution
}
else
{
// On est la seule instance de l'application
}

 

Sous Linux, il existe une fonction similaire pour créer un sémaphore, mais ce n'est pas la solution que j'ai retenue. Il faut savoir que sous Linux, en cas de crash de l'application, le sémaphore n'est pas libéré. C'est à l'utilisateur de saisir une commande shell pour libérer le sémaphore. J'ai donc préféré utiliser un simple fichier ouvert en création exclusive.

 
 auto handle = open(name, O_WRONLY | O_CREAT | O_EXCL);
if (handle < 0)
{
if (errno == EEXIST)
{
// Une autre instance est en cours d'exécution
}
else
{
// erreur
}
}
else
{
// On est la seule instance de l'application
}

 

Si le programme venait à planter et à ne pas supprimer le fichier, l'utilisateur n'aura qu'à supprimer ce fichier, ce qui est beaucoup plus accessible pour lui. Si vous connaissez une méthode plus sûre que celle-là, je suis preneur.

 

Comment communiquer entre processus ?

 

Sous Windows, la communication entre les processus est assurée par un pipe nommé. C'est assez simple à mettre en œuvre, surtout pour une communication dans un seul sens (client vers serveur). Je vous épargne le code de gestion des erreurs :

 

 // Code serveur
// Creation d'un pipe
auto handle = CreateNamedPipeW(toUTF16z(name),
PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_WAIT,
1,
2048,
2048,
0,
null);

// Attente d'une connexion d'un client
auto success = ConnectNamedPipe(m_handle, null);

// Lecture d'un message
success = ReadFile(handle, buffer.ptr, buffer.length, &bytesRead, null);

// Déconnecte immédiatement le client
DisconnectNamedPipe(m_handle);



// Code client
// Création d'un pipe
auto handle = CreateFileW(toUTF16z(name),
GENERIC_WRITE,
0,
null,
OPEN_EXISTING,
0,
null);
// Envoi d'un message
auto success = WriteFile(m_handle,
msg.ptr,
msg.length,
&bytesWritten,
null);

 

Sous Linux, les pipes nommés sont assez différents conceptuellement de leur homonymes Windows. J'ai donc utilisé les sockets UNIX à la place, qui fonctionnent comme les pipes nommés sous Windows. Là aussi, pour des raisons de concision, j'ai enlevé le code de gestion des erreurs.

 

 // Code serveur
// Création d'un socket serveur
auto handle = socket(AF_UNIX, SOCK_STREAM, 0);
sockaddr_un local;
local.sun_family = AF_UNIX;
strcpy(cast(char*)local.sun_path.ptr, szName);
unlink(szName);
uint len = cast(uint)(strlen(name) + local.sun_family.sizeof);
auto success = bind(ipc.m_handle, cast(const sockaddr*)&local, len);

success = listen(ipc.m_handle, 1);

// Attente d'une connexion d'un client
sockaddr_un remote;
uint t = cast(uint)remote.sizeof;
auto client_handle = accept(m_handle, cast(sockaddr*)&remote, &t);

// lecture d'un message
auto bytesRead = recv(client_handle, buffer.ptr, buffer.length, 0);

// déconnecte immédiatement le client
close(client_handle);



// Code client
// Création d'un socket client
auto handle = socket(AF_UNIX, SOCK_STREAM, 0);

// connexion
sockaddr_un remote;
remote.sun_family = AF_UNIX;
strcpy(cast(char*)remote.sun_path, szName);
uint len = cast(uint)(strlen(szName) + remote.sun_family.sizeof);
auto success = connect(ipc.m_handle, cast(const sockaddr*)&remote, len);

// Envoi d'un message
auto bytesWritten = send(m_handle, msg.ptr, msg.length, 0);

Conclusion

La mise en place d'une bonne intégration de Rossignol dans le menu des flux de Firefox a nécessité quelques aménagements dans le code, notament du code spécifique à chaque plate-forme. La capacité de D à appeler du code C directement sans passer par un wrapper à la JNI ou P/Invoke prend ici tout son sens.

 

Comme en C++, le RAII reste la meilleure méthode pour s'assurer de la bonne libération des ressources, que ce soit en utilisant le destructeur d'une structure sur la pile ou l'instruction scope.

Partager cet article

Repost 0
Published by Olivier - dans Projets personnels
commenter cet article

commentaires

Présentation

Recherche

Liens