Catégories
Langage

Multithreading avec GTK en python sur N900

Une application sous linux en python telle que Caritang doit résoudre le problème de réactivité de l’interface utilisateur en prenant en compte les spécificités suivantes:

  1. la thread principale qui execute GTK
  2. l’interpréteur python
  3. le serveur X

Une IHM se doit d’être réactive. Toute action de l’utilisateur doit provoquer une réponse immédiate de l’IHM. Or la mise à jour de l’IHM se fait quand la thread principal qui execute la boocle gtk sort du code appelé par l’évènement de l’interface graphique. Autrement dit ce n’est que lorsque le code créeé par le programmeur pour effectuer l’action est terminé qu’on est sûr que l’IHM sera mis à jour. Donc si le code de l’action n’est pas trés rapide alors on va monopoliser la thread principale de gtk à faire de l’applicatif au lieu de faire de l’IHM.
Quand on veut effectuer un traitement long, il faut lancer une thread dédiée à ce traitement afin de libérer la thread principale. Plusieurs techniques existent: on peut utiliser les fonctions utilitaires de gobject comme timeout_add ou bien une classe Timer ou alors créer soit même une Thread.
Quelque soit la technique il faut néanmoins indiquer à gtk qu’il va s,exécuter en multithreading en invoquant gtk.gdk.threads_init()

Libérer la thread principal permet à GTK de mettre à jour l’IHM. Ainsi si la thread du traitement modifie le texte d’un gtk.Label, la thread de GTK pourra mettre à jour l’IHM. Inversement l’IHM étant disponible, l’utilisateur peut demander la fermeture de l’application. Dans ce cas que se passe-t-il pour la thread de traitement? Soit elle continue en empêchant la fermeture du programme soit elle est brûtalement arrétée si elle est marquée comme deamon. Ce dernier cas n’est pas la meilleur solution car il entraine des erreurs dans la thread de traitement. Ainsi durant l’arrêt de l’interpréteur python, la thread de traitement se retrouve dépourvu des variables locales ou d’instances.
L’idéal est d’envoyer un signal à la tread de traitement pour qu’elle se termine proprement.

Enfin le serveur X (peut?) n’est pas « thread safe ». Malgrès tout ce qui précède il se peut que le thread principale de GTK accède à l’IHM en même temps que la thread de traitement. Une simple modification du texte d’un gtk.Label provoquera de manière aléatoire une erreur dans XLib. La solution est de protéger (synchroniser) les accès aux composants graphiques en encadrant toute modification de l’IHM dans la thread de traitement par gtk.gdk.threads_enter() et gtk.gdk.threads_leave()

Catégories
Paramétrage

Ajout d’un programme dans Hildon

Hildon est le nom de l’interface graphique du N900. Toute application qui veut s’intégrer à l’interface doit avoir une entrée dans la liste des programmes. Pour cela il n’est pas nécessaire de fabriquer une application « packagée » sous forme de paquet debian. Une simple installation de module python fait l’affaire.

La principale chose à faire est del créer un fichier du nom de l’application. Supposons que l’application s’appelle zourite. Il faut alors créer un fichier
/usr/share/applications/hildon/zourite.desktop
dont le contenu minimal sera:
[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Name=Zourite
Exec=/usr/bin/zourite

La ligne Exec peut contenir une ligne de commande complète avec argument telle que /usr/bin/caritang -g

Ce fichier suffit pour que Hildon ajoute une entrée dans la liste des programmes.
Liste des applications dans Hildon

Maintenant si on veut que Hildon affiche une jolie icone, il faut la lui fournir. Avec le fichier .desktop minimaliste, hildon va rechercher les icones à l’emplcement par défaut sous le nom par défaut, à savoir:

/usr/share/icons/hicolor/48x48/hildon/zourite.png
/usr/share/icons/hicolor/64x64/hildon/zourite.png

Avec un minimum d’effort il est maintenant possible d’ajouter un raccourci sur le bureau.

Ajout d'un raccourci sur le bureau Hildon

Catégories
Langage

Upload de vidéo sur Picasa en python

L’utilitaire caritang a pour objectif de permettre la sauvegarde des documents du N900 vers google docs. Mais il s’avère que picasa est plus adapté pour les média enregistrés avec la caméra du terminal qu’il s’agisse de photo ou de vidéo.

L’api gdata en version 2.0.9 permet depuis python de manipuler un compte picasa. Malheureusement la version de l’api picasa implémentée est la 1.0 alors que la 2.0 qui n’existe pas en python est la seule qui autorise l’envoi de vidéo.

Une impasse pour les vidéo ? Non, car le mécanisme d’envoi de vidéo est en fait, d’après la documentation de la 2.0, similaire à celui des photos modulo le type mime. Un test en utilisant le binding python officiel et en passant le type mime video/mp4 montre que le module gdata.photos.service vérifie que le type mime est bien une photo.

C’est la que la nature dynamique de python nous sauve. Il suffit de changer la liste (supposée être une constante) des type de fichier autorisés.

gdata.photos.service.SUPPORTED_UPLOAD_TYPES = gdata.photos.service.SUPPORTED_UPLOAD_TYPES + ("mp4",)

Dans la cas particulier du N900 l’obtention du type mime par le module mimetools echoue sur les mp4. Là encore on peut ajouter le type mime video/mp4 au module par une instruction

mimetypes.add_type("video/mp4", ".mp4")

Catégories
Langage

Upload de vidéo sur Picasa en python

L’utilitaire caritang a pour objectif de permettre la sauvegarde des documents du N900 vers google docs. Mais il s’avère que picasa est plus adapté pour les média enregistrés avec la caméra du terminal qu’il s’agisse de photo ou de vidéo.

L’api gdata en version 2.0.9 permet depuis python de manipuler un compte picasa. Malheureusement la version de l’api picasa implémenté est la 1.0 alors que la 2.0 qui n’existe pas en python est la seule qui autorise l’envoi de vidéo.

Une impasse pour les vidéo ? Non, car le mécanisme d’envoi de vidéo est en fait, d’après la documentation de la 2.0, similaire à celui des photos modulo le type mime. Un test en utilisant le binding python officiel et en passant le type mime video/mp4 montre que le module gdata.photos.service vérifie que le type mime est bien une photo.

C’est la que la nature dynamique de python nous sauve. Il suffit de changer la liste (supposée être une constante) des type de fichier autorisés.

gdata.photos.service.SUPPORTED_UPLOAD_TYPES = gdata.photos.service.SUPPORTED_UPLOAD_TYPES + ("mp4",)

Dans la cas particulier du N900 l’obtention du type mime par la module mimetools echoue sur les mp4. Là encore on peut ajouter le type mime video/mp4 au module par une instruction

mimetypes.add_type("video/mp4", ".mp4")

Catégories
Gestion de projet Langage

Promouvoir son module python avec Pypi

Zourite est à un stade encore immature mais peut être une vitrine de ce que sera l’application. Comme tout application Maemo, sa place serait dans le dépot extra-dev afin qu’elle puisse être installée depuis le gestionnaire d’application du terminal. Hélas la création de compte sur le site de dev de maemo.org (le garage) semble ne pas fonctionner. De plus, l’intégration d’un développent au processus de build du garage n’est pas trivial.

Heureusemenr que Zourite est écrite en python si bien qu’elle peut bénéficier de l’index central des modules. Le site Pypi est un dépot de module (sorte de repo pour les programmes python). Il suffit de s’inscrire pour disposer d’un couple identifiant/mot de passe et pouvoir rendre publique son module.

Les contraintes sont minimes et vont d’ailleurs dans le bon sens. Pypi oblige en effet d’avoir un setup.py bien écrit avec des attributs standardisés comme la plateforme ou la licence du module. Les bénéfice en terme de visibilité en valent la chandelle.

Catégories
Gestion de projet

Packaging d’application python sous maemo 5

Dans le monde java, le packaging d’application est dicté par les standard java. On fait des JAR, des WAR, des EAR… L’ecosystème étant borné par la JVM et le conteneur (web ou ejb), la question du livrable ne se pose guère. Mais en python sous linux comment fait-on ?

La première idée est de générer un paquet pour la distribution cible. En l’occurence il s’agissait de l’application Zourite pour N900. On s’oriente donc vers la création de paquet debian. La création de paquet peut se faire via l’IDE Esbox. Cependant cela n’est pas si simple et un ignorant en packaging linux-debian n’y arrive pas sans faire un petit tour sur le site de debian pour se rendre compte que la création de paquet s’apparante à la création de distribution et que c’est un rôle distinct de celui de développeur d’application.

Heureusement que python (à ma grande surprise pour un vieux langage) dispose d’un écosystème riche à la java. Python inclut un mecanisme de création de livrable par le biais du fameux fichier setup.py. Un détour par la documentation en ligne et un peu de persévérence permet d’arriver à ses fins.

Le fichier setup.py contient la description du paquet python. Il est conseillé d’avoir au préalable organisé les sources du projet en modules dans un paquet dédié. Cela permettra d’embarquer tous les fichiers dans le paquet (répertoire) d’installation. Le fichier setup.py peut installer également des scripts de lancement du programme python. Ces scripts sont eux-même des script python qui pourront changer le répertoire courant du processus pour se rendre dans le répertoire du paquet de l’application afin que l’execution se passe comme en développement.

Voici un exemple de fichier setup.py:

# -*- encoding: UTF-8 -*-
'''
Created on 30 mars 2010

@author: thierry
'''
from distutils.core import setup
setup(name='zourite',
version='0.1.0',
package_dir = {'': 'src'},
packages=['zourite',
'zourite.common',
'zourite.core',
'zourite.gui',
'zourite.plugin',
'zourite.plugin.gmail',
'zourite.plugin.linkedin'],
scripts=['scripts/zourite','scripts/zourite-setup'],
package_data={'zourite': ['*.png']},
author='Thierry Bressure',
author_email='thierry@bressure.net',
maintainer='Thierry Bressure',
maintainer_email='zourite@bressure.net',
url='http://blog.zourite.bressure.net',
download_url='http://zourite.bressure.net',
description='Zourite Professional Networking Application for N900',
long_description='Zourite is a client for professional Network like LinkedIn. You can stay connected to your contacts and manage you network.',
classifiers=[
'Development Status :: 1',
'Environment :: Hildon',
'Environment :: Maemo',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: Developers',
'License :: OSI Approved :: LGPL',
'Operating System :: Maemo :: Maemo 5',
'Programming Language :: Python',
'Topic :: Communications :: Email',
'Topic :: Office/Business',
],

)

Catégories
Gestion de projet Langage

Révision globale subversion dans un projet en python chez googlecode

Il est courant dans un projet d’insérer dans les sources la révision du fichier. Cela est accomplit par le système de versionnage de source par un mécanisme de substitution d emot clé. Par exemple le mot clé Revision balisé par $Revision:$ est automatiquement remplacé par subversion par $Revision: 70$ si la révision du fichier est 70.

Ce mécanisme est très utile si on veut pouvoir raccroché un source à un révision en dehors de l’environnement de développement. Cela est en l’occurence utile quand on souhaite remonté un bug dans un projet en python. Imaginons un projet ecrit en python. Python étant un langage interprété, l’application est distribuée sous forme de fichiers sources .py. Lorsqu’une exception survient, python peut rapporter la pile d’execution constituant un début rapport d’erreur. Mais il faut encore associer cette pile à une version de l’application. Quand l’application est en version finale, on peut inclure dans le livrable la version commerciale de l’application mais il est encore plus facile pour le développeur d’inclure dans le rapport la révision des sources. Et c’est encore plus vrai quand l’application est en phase de développement ou de beta-test.

C’est exactement ce qui m’arriva avec Zourite, une application de réseautage professionnel pour N900. Zourite contient un mécanisme de rapport de bug qui envoie la pile d’erreur au site du développement du projet. Ce dernier étant hébergé par googlecode, je n’ai pas d’accès aux mécansime de hook subversion. Je dois donc me contenter de la substitution de révision qui ne fournit pour un fichier que la plus récente révision où ce fichier a été modifié. Or la pile d’éxecution fait appel à plusieurs fichiers donc afin que le développeur reproduise le bug, il me faut absolument la révision gloable des sources qui ont produit le bug.

Subversion fournit un outil svnversion mais celui-ci doit être executé sur un copie local des sources donc il n’est pas utilisable avec l’application livrée. De plus n’ayant pas accès au serveur subversion de googlecode, il m’est impossible d’envisager utiliser un hook pour automatiser le renseignement de la révision globale du projet.

Mais heureusement python contrairement à Java utilise la notion d’import dans son sens originel c’est-a-dire monter en mémoire un module et non pas comme un simple raccourci de notation comme en Java. Donc on peut se servir des instruction d’import des module python pour que l’application parcours tous les modules la composant. Si chaque module va proposer sa révision à un module centralisant les révisions, on est capable de connaitre la révision globale du projet… à l’éxécution.

La mise en place du mécanisme de substitution est assez aisé. Pour un ajout automatique à un nouveau fichier, suivre les instructions ici pour lesquels nous retiendrons les étapes suivantes:

  1. ouvrir le fichier ~/.subversion/conf
  2. dans la section [miscellany] décommenter la ligne enable-auto-props = yes
  3. dans la section [auto-props] ajouter la ligne *.py = svn:eol-style=native;svn:keywords=Revision

Malheureusement les fichiers qui sont déjà sous subversion ne seront pas affectés. Il faut donc un a un leur rajouter la propriété de substitution. Dans ESBox il faut selectionner le fichier et dans le menu contextuel ouvrir le menu Team > Set property:

Ajout propriété svn à un fichier dans ESBox

Puis selectionner pour property name la propriété svn:keywords et pour property content la valeur Revision

Répéter l’opération pour tous les fichiers où subversion doit appliquer la substituoin par mot clé.

Maintenant nos pouvons ajouter le mot clé $Revision$ dans les sources python. Cette valeur sera reprise par chaque module et soumis à un module centrale qui portera la révision globale.

Voic le code source du module centrale qui lui même renseigne sa révision à lui-même !

# -*- encoding: UTF-8 -*-
'''
Created on 28 mars 2010

@author: thierry
'''

CURRENT_VERSION = "1.0-SNAPSHOT"  # this must be modified before any release

def getInstance():
 return SINGLETON

class Version(object):
 '''
 Hold the revision of the application.
 '''        

 def __init__(self):
 '''
 Constructor
 '''
 self.revision = ""

 def submitRevision(self, rev):
 '''
 submit a new revision. If the revision is upper than
 the current revision, it become the new current revision
 '''

 if self.revision < rev:
 self.revision = rev

 def getRevision(self):
 '''
 Return the scm revision for this application
 '''
 return self.revision

 def getVersion(self):
 '''
 Return the marketing version for this application.
 '''
 return CURRENT_VERSION

SINGLETON = Version()  # object that track the revision of current applcation for issue tracking purpose

getInstance().submitRevision("$Revision: 73 $")

Chaque module doit alors à l’instar du module version lui-même, renseigner sa révision. Ainsi chaque module devra contenir en dernière instruction d’import le code suivant:

import version
version.getInstance().submitRevision("$Revision: 74 $")

Quand on aura besoin de connaitre la révision globale de l’application pour soumettre un bug par exemple, il suffit de faire :

versionInstance = version.getInstance()
versionStr = versionInstance.getVersion()
revisionStr = versionInstance.getRevision()
Catégories
Gestion de projet Langage

Révision globale subversion dans un projet en python chez googlecode

Il est courant dans un projet d’insérer dans les sources la révision du fichier. Cela est accomplit par le système de versionnage de source par un mécanisme de substitution d emot clé. Par exemple le mot clé Revision balisé par $Revision:$ est automatiquement remplacé par subversion par $Revision: 70$ si la révision du fichier est 70.

Ce mécanisme est très utile si on veut pouvoir raccroché un source à un révision en dehors de l’environnement de développement. Cela est en l’occurence utile quand on souhaite remonté un bug dans un projet en python. Imaginons un projet ecrit en python. Python étant un langage interprété, l’application est distribuée sous forme de fichiers sources .py. Lorsqu’une exception survient, python peut rapporter la pile d’execution constituant un début rapport d’erreur. Mais il faut encore associer cette pile à une version de l’application. Quand l’application est en version finale, on peut inclure dans le livrable la version commerciale de l’application mais il est encore plus facile pour le développeur d’inclure dans le rapport la révision des sources. Et c’est encore plus vrai quand l’application est en phase de développement ou de beta-test.

C’est exactement ce qui m’arriva avec Zourite, une application de réseautage professionnel pour N900. Zourite contient un mécanisme de rapport de bug qui envoie la pile d’erreur au site du développement du projet. Ce dernier étant hébergé par googlecode, je n’ai pas d’accès aux mécansime de hook subversion. Je dois donc me contenter de la substitution de révision qui ne fournit pour un fichier que la plus récente révision où ce fichier a été modifié. Or la pile d’éxecution fait appel à plusieurs fichiers donc afin que le développeur reproduise le bug, il me faut absolument la révision gloable des sources qui ont produit le bug.

Subversion fournit un outil svnversion mais celui-ci doit être executé sur un copie local des sources donc il n’est pas utilisable avec l’application livrée. De plus n’ayant pas accès au serveur subversion de googlecode, il m’est impossible d’envisager utiliser un hook pour automatiser le renseignement de la révision globale du projet.

Mais heureusement python contrairement à Java utilise la notion d’import dans son sens originel c’est-a-dire monter en mémoire un module et non pas comme un simple raccourci de notation comme en Java. Donc on peut se servir des instruction d’import des module python pour que l’application parcours tous les modules la composant. Si chaque module va proposer sa révision à un module centralisant les révisions, on est capable de connaitre la révision globale du projet… à l’éxécution.

La mise en place du mécanisme de substitution est assez aisé. Pour un ajout automatique à un nouveau fichier, suivre les instructions ici pour lesquels nous retiendrons les étapes suivantes:

  1. ouvrir le fichier ~/.subversion/conf
  2. dans la section [miscellany] décommenter la ligne enable-auto-props = yes
  3. dans la section [auto-props] ajouter la ligne *.py = svn:eol-style=native;svn:keywords=Revision

Malheureusement les fichiers qui sont déjà sous subversion ne seront pas affectés. Il faut donc un a un leur rajouter la propriété de substitution. Dans ESBox il faut selectionner le fichier et dans le menu contextuel ouvrir le menu Team > Set property:

Ajout propriété svn à un fichier dans ESBox

Puis selectionner pour property name la propriété svn:keywords et pour property content la valeur Revision

Répéter l’opération pour tous les fichiers où subversion doit appliquer la substituoin par mot clé.

Maintenant nos pouvons ajouter le mot clé $Revision$ dans les sources python. Cette valeur sera reprise par chaque module et soumis à un module centrale qui portera la révision globale.

Voic le code source du module centrale qui lui même renseigne sa révision à lui-même !

# -*- encoding: UTF-8 -*-
'''
Created on 28 mars 2010

@author: thierry
'''

CURRENT_VERSION = "1.0-SNAPSHOT"  # this must be modified before any release

def getInstance():
 return SINGLETON

class Version(object):
 '''
 Hold the revision of the application.
 '''        

 def __init__(self):
 '''
 Constructor
 '''
 self.revision = ""

 def submitRevision(self, rev):
 '''
 submit a new revision. If the revision is upper than
 the current revision, it become the new current revision
 '''

 if self.revision < rev:
 self.revision = rev

 def getRevision(self):
 '''
 Return the scm revision for this application
 '''
 return self.revision

 def getVersion(self):
 '''
 Return the marketing version for this application.
 '''
 return CURRENT_VERSION

SINGLETON = Version()  # object that track the revision of current applcation for issue tracking purpose

getInstance().submitRevision("$Revision: 73 $")

Chaque module doit alors à l’instar du module version lui-même, renseigner sa révision. Ainsi chaque module devra contenir en dernière instruction d’import le code suivant:

import version
version.getInstance().submitRevision("$Revision: 74 $")

Quand on aura besoin de connaitre la révision globale de l’application pour soumettre un bug par exemple, il suffit de faire :

versionInstance = version.getInstance()
versionStr = versionInstance.getVersion()
revisionStr = versionInstance.getRevision()
Catégories
Application

Sauvegarde des documents N900 sur google doc

Google propose depuis peu de stocker tout type de document sur son service Google Docs. Si la capacité est limitée à 1 Go dans sa version gratuite, pour 5 $ on passe à 20 Go. Cette capacité est alors assez confortable pour stocker les documents et média enregistrés avec la caméra du terminal. Cependanr pouquoi payer alors qu’un compte gmail offre déjà un quota de 7 Go? Parce que gmail a une limite de taille de pièce jointe à 20 Mo alors que google docs pousse la taille max des documents à 250 Mo.

Une recherche sur… Google nous montre qu’une solutions existe pour le N900 en couplant 2 logiciels: Petrovich et Pixelpipe. Mais l’idéal serait un mécanisme automatisée. Autrefois je m’aurais tourné vers un développement Java mais le temps est au Python. Cela tombe bien puisque google fournit une implémentation Python de son API gdata.

Un premier parcours de cette API montre qu’elle permet d’accéder à presque tous les services de Google depuis la base de contacts jusqu’à Youtube. Cela ouvre des perspectives pour les développeurs.

En revenant sur la problématique de sauvegarde, on peut imaginer écrire un script python qui prend les fichiers contenus dans les réepertoires DCIM des partitions pour les mettre dans google docs. Il faudrait prévoir la création d’un répertoires N900 dans google docs afin de bien identifier ces fichiers.

Pour finir ce billet, je dirai que même si mon propos est orienté vers le N900, un tel scripte python marche aussi sur tout autre de bureau.

Catégories
Langage

3 mois dans le panier de python

J’ai commencé à développer en python par nécessité. En effet il y a 3 mois, j’avais le choix entre le C et le Python pour développer pour le N900. Dans les 2 cas les outils existent au travers de l’IDE Esbox. Bien que le C soit officiellement le langage consacré par Nokia pour sa plateforme Maemo, cette dernière bénéficie d’une communauté suffisamment active pour proposer un portage de python avec les binding GTK qui vont bien. J’ai choisi Python car c’est la solution la plus facile pour un développeur java.

Cependant il y a certains aspects à prendre en compte rapidement surtout quand on est habitué à Java.

  1. langage de scripte donc interprété donc erreurs de link visibles qu’au moment de l’execution
  2. pas de typage des variable et attributs
  3. adhérence au c et bibliothèques système
  4. pas d’interface mais héritage multiple

Ces précisions ont leur avantages et leurs inconvénients. Certains obligent à plus de discipline dans l’organisation du code comme le manque d’interface alors que d’autres offrent une implémentation directes de décorateurs grâce au pointeur de fonction et à la forme lambda.

J’apprécie de plus en plus ce langage pour sa productivité et la rapidité du code à l’execution.