Nus utilisons le Javascript en se basant sur la technologie XUL open source de Mozilla.
Les applications que nous utilisons pour développer Freedom Offline se base sur la technologie xul de Mozilla. Une application xul se compose de différents fichiers:
des fichiers xul : pour la mise en forme du contenu de l'application (similaire à html)
des fichiers javascript : pour la partie algorithmique.
des fichiers css : pour décrire la présentation de l'application.
des fichiers dtd : pour la traduction des label utilisés dans les fichiers xul.
des fichiers properties : pour la traduction des label utilisés dans les fichiers javascript.
des images.
des fichiers de configurations (qui diffèrent selon le paquetage).
Il faut savoir aussi que le paquetage des applications pour Windows, pour Linux et l'extension Firefox n'est pas tout à fait la même. Nous signalerons dans ce paragraphe les différences entre chacune.
Donc a la base on a:
un exécutable qui permet de lancer l'application (copie de xulRunner-stub.exe du dossier xumRunner).
le fichier application.ini qui est un fichier de configuration qui décrit l'application.
le dossier 'xulrunner' qui contient le noyau XULRunner (contenu différent selon
OS).
le dossier 'chrome' qui contient le code source de l'application, on y revient plus tard.
le dossier 'data' est un dossier dont on se sert pour stocker mes données, on y revient aussi après.
le dossier defaults/préférences contient un fichier prefs.js qui ajoute à XULRunner des préférences et particulièrement spécifie le chemin d'accès par défaut pour lancer l'application.
le dossiers 'extensions' est un dossier qui permet de stocker des extensions de la bibliothèque d'objets XPCOM de Mozilla.
le dossier 'updates' sert aux évolutions de l'application.
Une extension Firefox est en faite une fichier compressé au format 'zip' renommé en 'xpi'. Voici ce qu'il y a dans ce paquet 'xpi':
A la base on a le dossier chrome et deux fichiers de configuration, le premier est le fichier install.rdf qui est utilisé par Firefox pour déterminer les informations d'installation de l'add-on. Il contient les méta-données identifiant l'add-on, la personne qui l'a créée et la version avec laquelle l'application Firefox est compatible. Le second fichier de configuration est le chrome.manifest, Ce fichier manifest décrit le paquetage et relie son emplacement sur le disque à son adresse URL chrome.
Il contient les sources de l'application en elle-même:
le dossier content contient tous les fichiers Javascript et
XUL.
le dossier 'locale': contient les fichiers de localisation, pour la traduction de l'application en plusieurs langues. Dans mon application, la traduction a été faite seulement en français et anglais mais cette architecture permet de rajouter une langue très facilement.
le dossier 'skin' contient toutes les images et
CSS de l'application.
Le dossier 'icons' permet quant à lui permet de stocker les icônes utilisés pour les en-têtes de fenêtre.
L'authentification est lancée au chargement de la fenêtre principale de l'application. Celle ci est assez différente si l'on est connecté au serveur de Freedom ou s'il est impossible d'atteindre celui-ci. On présente ici le mécanisme dans le cas le plus commun.
Explication sur le fonctionnement: Au chargement est lancé la fonction authentification(), cette fonction ouvre la fenêtre d'authentification. Cette dernière va, à son chargement, remplir les champs login, mot de passe, url, avec les informations que l'utilisateur aurait pu avoir enregistrées auparavant. Quand l'utilisateur clique sur 'OK', la fonction de retour doOK() repasse la main à la fenêtre principale en lançant onAuthentOK(). Cette fonction va appelé la fonction detectNetwork() pour essayer d'atteindre le serveur se trouvant à l'URL donnée par l'utilisateur lors de l'authentification. Quand il reçoit la réponse il notifie un observateur avec un message 'connecté' ou 'non connecté'.
Cette observateur va à son tour, soit appelé authentOnline() ou bien authentOffline() selon l'état de connexion au serveur.
C'est à partir de là que l'on peut faire une authentification différente selon que l'on est connecté ou non. En cas de connexion, il est facile de faire l'authentification, Il suffit d'appeler une méthode de la couche data pour s'authentifier. Cette couche permet la communication avec le serveur.
Dans le cas où le serveur est impossible à atteindre, Cela est dû peut être dû à deux cas biens distincts:
Dans tous les cas, on utilise le composant d'enregistrement des mots de passe et login de Mozilla pour comparer les données correspondant à l'url déjà dans la base et ceux que l'utilisateur a donné. Si le triplet [login, mot de passe, url] existe dans le 'loginManager' de Mozilla, l'authentification a réussi. Dans le cas où l'application ne trouve rien dans la base de Mozilla, alors l'authentification a échoué et on affiche de nouveau le fenêtre d'authentification.
Il est plus facile de comprendre les étapes de la synchronisation avec le graphe d'états qui suit.
Ce qu'il faut bien avoir en tête pour mieux comprendre la synchronisation, c'est que l'on construit à la première étape une liste de descriptions des documents à synchroniser. Cette description est un objet JSON (Javascript Object Notation) qui va permettre d'avoir toutes les informations dont on a besoin sur un document à synchroniser, on a dans cet objet le nom du document, son identifiant (docId), une liste des fichiers qu'il contient, l'action à faire sur ce document (exemple: 'à envoyer', 'à télécharger', 'ne rien faire', ou 'conflit') et s'il est en conflit on y rajoute la cause du conflit ('modifié', 'verrouillé', 'supprimé', 'inaccessible').
Cette objet JSON est la clé de la synchronisation, il est passé d'étape en étape et on travaille dessus à toutes les phases de la synchronisation. Il est créé au début par une fonction comparLastModifDoc() qui en faite compare les date de dernière modification du document sur le serveur, la date de modification sur le poste client de chaque fichier contenu dans ce document et la date de synchronisation. À partir de ces trois dates on peut savoir ce qu'il y a à faire sur le document. On y rajoute, au passage, des tests pour savoir si le document n'a pas été supprimé sur le serveur, ou si les droits que l'on a sur le document n'ont pas été modifiés depuis la dernière synchronisation…
La partie difficile de la synchronisation est le côté asynchrone de la phase d'envoi et de téléchargement.
En effet durant l'envoi, ce que l'on fait est en faite une sauvegarde des documents modifiés sur le serveur, c'est-à-dire que l'on envoie un par un les fichiers des documents modifiés sur le serveur pour qu'il remplace les anciens fichiers. Le problème est que l'on ne peut pas envoyer des requêtes simultanées de sauvegarde de fichier pour un seul document. Il faut donc mettre en place des fonctions de callback après chaque sauvegarde de fichier effectuée correctement pour pouvoir commencer à envoyer le fichier suivant.
La phase de téléchargement est à peu près la même, on ne connaît pas à l'avance le temps que l'on va mettre pour télécharger tous les fichiers. On lance cette fois ci les requêtes de téléchargement en même temps sauf que pour savoir quand le dernier fichier est arrivé, il faut mettre en place un système de compteur que l'on incrémente à l'envoi de chaque requête et que l'on décrémente à la réception de chaque fichier. Quand le compteur revient à zéro, cette phase de téléchargement est terminé.
La base de données contient plusieurs tables:
'docId' l'identifiant du document,
'document' qui est un objet JSON représentant le document Freedom,
'access' qui décrit l'état du verrou du document
'modified' qui dit si un document a été modifié ou pas.
'searchString' qui est une chaîne de caractère qui est le résultat de la concaténation du titre du document, des titres de fichiers et de la famille, tout en minuscules et sans accent Cela en vue d'améliorer la rapidité quand un utilisateur fait une recherche.
'lastSynchTime': la date de dernière synchronisation avec le serveur.
La table des fichiers contenant:
'docId' qui fait référence à l'identifiant du document dont le fichier fait partie
'attribute' qui est le nom de l'attribut du document dont le fichier est la valeur
'indexAttribute'. En général un attribut fichier ou image ne contient qu'un fichier dans ce cas l'index d'attribut est 0, cependant il existe des cas où l'attribut contient une liste de fichiers (exemple: un document contenant une liste de fichiers annexes). Dans ce cas l'index d'attribut est la position dans la liste. Cela est utile quand on envoie le fichier sur le serveur lors de la synchronisation, à ce moment là il faut préciser quel attribut on change et quel est sa place dans la liste (si l'attribut est une liste).
'file'. Le nom du document.
'visibility'. La visibilité de l'attribut. Il y a plusieurs type de visibilité dans Freedom et c'est grâce à cela que l'on va déterminer si un fichier peut être modifié ou s'il peut être sauvé sur le serveur lors de la synchronisation.
La table des applications préférées. Cette table est remplie lorsque l'utilisateur ouvre un fichier avec une application qu'il a fourni lui-même et qu'il a sauvegardée comme application par défaut. Elle contient:
'fileType' qui est le type de fichier. Exemple 'odt', 'doc'…
'appliPath' qui est le chemin de l'application à utiliser pour ouvrir ce type de fichier.
La table gardant les informations de l'utilisateur, cette table est utilisée lors de l'authentification en mode déconnecté, elle contient:
'login'. Le nom de l'utilisateur.
'url'. L'adresse
URL de l'application Freedom
'client'. Le nom de l'entreprise pour laquelle Freedom a été installé. Cela est utile pour afficher dans l'en-tête de la fenêtre principale, le nom de l'entreprise.
'lastOne'. Cette colonne peut prendre les valeurs 0,1,2. elle permet de remplir les champs d'authetification. Si lastOne = 1, on va remplir les champs avec ce login et cette url, si lastOne = 2, on remplie de la même façon en y ajoutant le mot de passe. Quand on ne veut pas sauvegarder nos informations, cette ligne est à 0. dans cette table, il n'y a qu'une seule ligne qui est à 1 ou à 2, les autres sont à 0.
Pour les applications Windows et Firefox, je stocke les données dans le dossier data, pour l'extension Firefox, je stocke ces données dans le profil Firefox ( ~/.mozilla/firefox/<profileId> sur Linux) du poste client.
Les données sont composées:
d'une base de données propre à chaque utilisateur et adresse
URL.
Un dossier propre à chaque utilisateur et adresse
URL (comme la base de données) contenant les fichiers de chaque document synchronisé. Ci-dessous un exemple d'arborescence:
Qu'est ce que l'on peut voir sur cette capture d'écran?
La base de données et le dossier contenant l'ensemble des documents porte dans leur nom le nom de l'utilisateur 'admin' et l'
URL 'localhost_Freedom' (qui provient de (
http://localhost/Freedom, adresse de l'application)
un fichier Freedom_login: qui permet de retenir les données d'authentification de l'utilisateur. (une amélioration pourrait être de mieux cacher ces données ou d'utiliser l'objet XPCOM de XULRunner pour l'enregistrement des mots de passe).
Chaque document est représenté par son identifiant initial (différent de l'identifiant courant qui change à chaque nouvelle version d'un document) . On utilise cette identifiant initial au lieu de l'identifiant courant pour des raisons de commodité. En effet lors de la synchronisation si l'on sauve le fichier sur le serveur, on ajoute une révision au document ce qui change l'identifiant courant du document. Hors, après avoir sauvé un document, il faut le télécharger de nouveau pour le remettre à jour localement c'est donc à ce moment là que l'on peut retrouver facilement le document dans sa version précédente. L'autre utilité de classer les fichiers par dossier représentant un document, c'est de pouvoir avoir plusieurs fichiers qui ont le même nom dans la base local et ne pas éviter des confusions.
dans un dossier représentant un document, il y a tous les fichiers ou images de ce document plus l'icône du document (utilisé pour l'affichage du document dans la grille). Certains fichiers sont verrouillés. En effet, dans un document tous les fichiers ne sont pas éditables(l'icône par exemple) et donc au téléchargement on modifie les droits sur ce fichier. Même si quelqu'un réussit à modifier un fichier qu'il ne peut pas à l'origine modifier, un autre contrôle est fait lors de la synchronisation, seulement les fichiers éditables sont renvoyé sur le serveur.