CiviKey évolue encore un peu

Comme je vous le disais il y a pile un mois (ou un peu plus), une version 2.6.0 de CiviKey est dans les cartons pour la nouvelle année.

Et bien une nouvelle release de la distribution Alpha est disponible (la 2.6.1). Cette release apporte des corrections de bugs dans différents plugins, et surtout de nouvelles fonctionnalités !

Une aide intégrée, facile à utiliser, et bientôt facile à éditer.

 

Un éditeur de clavier beaucoup plus puissant.

Il permet maintenant de créer, modifier, supprimer des touches, des zones, ainsi que leurs layouts etc etc etc. Bref vous pouvez (presque) tout faire avec !

 

 

Niveau stabilité, il reste un peu de chemin à faire afin de corriger les derniers bugs qui peuvent survenir lors de l'utilisation des nouveaux plugins. Cependant le plus dur est fait, il reste maintenant à passer les derniers coups de polish et nous serons près d'ici 2013 pour vous livrer une nouvelle version !

On vous tient au courant :)

Comme la dernière fois, n'hésitez pas à lire le code sur GitHub ou à nous suivre sur Twitter ou Facebook

Une version 2.6.0 de CiviKey pour 2013

Et oui, outre la sortie du nouveau site officiel en aout dernier et un léger problème de mise à jour automatique, CiviKey continu d'évoluer !

Grâce au soutien de la Fondation Garches et d'Alcatel Lucent, CiviKey passera pour la nouvelle de la version 2.5.2 à la version 2.6 !

Une 2.6, pas une 3.0 ?

Et non, pas une 3.0 car aucun changement majeur n'a été fait au sein du noyau depuis la grande bataille de la 2.5. L'objectif de la 2.6 est d'ajouter à CiviKey différents plugins indispensables aux ergothérapeutes et personnes en situations de handicap qui l'utilisent au jour le jour.

Au menu de cette 2.6 les fonctionnalités que tout bon clavier visuel se doit d'avoir : une prédiction de mot, et une solution de défilement des touches du clavier. S'ajoute à cela quatre nouveautés :

  • un clavier permettant grâce au défilement de déplacer et d'effectuer opérations de base de la souris
  • un assistant au premier démarrage
  • un assistant création 
  • et édition de clavier, et enfin une aide contextuelle intégrée

D'ici 2013 ... et avant ?

La sortie finale de la version 2.6 est prévu pour 2013. Vous souhaitez d’ores et déjà essayer nos builds fraichement sortis du four ?

Contactez-nous et nous vous fournirons un installeur spécifique pour la pre-release. Cette alpha version:

  • est déployée parallèlement à tout autre installation existante;
  • se mettra à jour sur notre "canal alpha", un canal de mise à jour différent de celui de la version stable actuelle.

En attendant voici quelques captures des derniers plugins !

La prédiction et défilement fonctionnant sur la skin WPF

Le clavier souris avec le défilement

La première étape de l'assistant création et édition de clavier

 

Evidemment pour ne pas perdre la tradition des posts technique sur l'architecture et l'implémentation de CiviKey de ce blog, deux posts suivront dans les prochaines semaines.

Le premier fera le point sur l'implémentation actuelle du défilement, les concepts sous-jacents ainsi que les évolutions prévues. Et enfin le second plongera dans les tréfonds de la prédiction de mot et explicitera comment cette fonctionnalité majeure est intégrée au sein de notre architecture de plugins. Vaste programme !

En attendant, n'hésitez pas à lire le code sur GitHub ou à nous suivre sur Twitter ou Facebook

La faute de frappe

Un module de mise à jour…

CiviKey a été mis en ligne sur son nouveau site www.civikey.fr il y a quelques semaines.
Avant cela, il avait été distribué au sein de l’hôpital de Garches et d’Alcatel-Lucent.
Il comporte depuis longtemps un module d’update,  qui va chercher la dernière mise à jour disponible sur internet, et la met dans un dossier temporaire de la machine utilisateur pour l’exécuter au lancement suivant.

Ce module avait été éprouvé lors de sa distribution limitée, il a fait ses preuves et est fonctionnel.
Avant la mise en ligne sur le site, nous avons fait une petite refonte du produit, et notamment du module d’update, rien de bien méchant, un déplacement de quelques classes, des commentaires en plus.

…Qui ne met pas à jour

La semaine dernière, nous avons décidé de pousser un nouveau setup sur le serveur, pour intégrer le web installer du Framework .NET 4.0 Client Profile à l’installeur CiviKey, ayant eu beaucoup de retours d’utilisateurs ne comprenant pas la marche à suivre pour le mettre en place.

L’équipe de test s’est rendue compte avec un bonheur évident qu’après avoir téléchargé une mise à jour, L’application ne la lançait pas, et pire, proposait à nouveau de télécharger cette mise à jour. « Pourtant, le fichier de mise à jour est bien présent dans le bon dossier. Voilà qui est étrange ! »

Le code qui posait le fichier de mise à jour semblait correct :

« Le fichier Update.exe, qui contient l’installeur de la mise à jour, est bien mis dans Temp/CiviKey/Standard/Updates… »


Avec anxiété, nous sommes allés voir le code qui va lancer cette mise à jour, et là, nous sommes tombés sur :



Trouvé ?

 

Attendez… avec un zoom c’est plus facile.





 

…Updares ? Sérieusement ?

La légère refonte a inséré une jolie faute de frappe au seul endroit absolument crucial de l’application.

A cause d’elle, le module télécharge la mise à jour, mais cette mise à jour n’est pas détectée au lancement suivant. Le module de mise à jour, très simple, détecte à nouveau une mise à jour et propose fatalement de la télécharger à nouveau. On obtient donc un prompt inutile et incompréhensible à chaque lancement de l’application.

Evidemment, on ne peut pas régler le problème en lançant une mise à jour, puisque le bug est précisément dans le module  de mise à jour.

Même les meilleurs font des erreurs

Et qui a fait ça ? Un stagiaire étourdi ? Un dyslexique avéré ?
Non, le patron d’Invenietis lui-même, 25 ans de métier  et deux-trois développements plutôt trapues derrière lui, dont un gestionnaire de portails web dynamique, un moteur de plugins avec gestion des cycles d’interdépendance (intégré à CiviKey d’ailleurs) et bien d’autres outils dont la seule vue du code pourrait scotcher toute personne normalement constituée. Il vous suffit de regarder quelques-uns des autres posts de ce blog pour vous rendre compte qu’on n’a pas affaire ici à un débutant et que le souci du détail ne lui est pas inconnu.

Et où étaient les tests à ce moment-là ? 
« C’est Olivier qui a codé, on ne pensait pas que ça pouvait être buggé. »

Bien joué.

Etat des lieux

Invenietis a pêché par excès de confiance. Résultat : tous les gens ayant téléchargé l’application (et dont nous n’avons pas les coordonnées bien entendu), devront la télécharger à nouveau manuellement pour récupérer une version de CiviKey capable de se mettre à jour automatiquement.

Le projet est encore en béta, et est gratuit, ce n’est donc pas un drame et les utilisateurs savent que des problèmes peuvent encore apparaitre.
Les gens intéressés par le projet suivront son évolution, et les différents hôpitaux et ergothérapeutes s’en arrangeront probablement.
Les développeurs, eux, se sentent tout de même très  mal.

Une leçon à en tirer : Même si Linus Torvald lui-même pose trois lignes de code dans votre application, testez-la.

Une question subsiste : « Qui a mis cette touche ‘r’ à côté du ‘t’ » ?

I just can't (Nu)Get enough!

NuGet, pour ceux qui ne connaitrait pas encore est un projet Open Source sponsorisé par Microsoft qui adresse (enfin) le "packaging" de composants logiciels. Tout le monde en a besoin et cela manquait cruellement à l'eco-système .Net.

Ce projet est prometteur sur de nombreux aspects mais il n'adresse qu'une petite partie des enjeux. Ce post est, je l'espère, le premier d'une petite série dont l'objectif est de rendre compte de notre expérience à Invenietis en proposant des évolutions concrètes et/ou des compléments qui nous semblent cruciaux. Nous ne sommes pas les seuls à espérer plus, par exemple Davy Brion qui soulève le problème d'une dépendance à "au moins un de ces packages". Tout ce qui concerne le packaging et la gestion de dépendances n'est pas simple. Il est en fait plus souvent complexe que ce que l'on peut penser car il met en jeu notamment :

  • La stratégie de versionning des composants logiciels. Si Microsoft a donné un cadre général avec l'objet Version, il reste :
    • A décider de ce que l'on met dans les quatres nombres Major.Minor.Build.Revision (dans cet ordre! voir cette question sur stackoverflow), et ce n'est pas le plus simple (voir également cette question et ses réponses)
    • A prendre en compte qu'un assembly possède en réalité deux Versions: celle de l'Assembly proprement dite et celle du fichier (ce qui est expliqué ici). Pour faire simple :
      • La Version est utilisée par le chargeur d'Assembly de .Net. Lorsqu'une référence est spécifiée avec <specificVersion>true</specificVersion> (ce qui est le défaut), il faut que l'assembly soit exactement compatible (à propos de cette spécification de version et ses impacts sur les temps de compilation, ce billet vaut le détour).
        Cette information est stockée dans le manifeste de l'Assembly : c'est donc du "pure .Net".
      • FileVersion est plutôt pour "l'affichage"... certes mais attention : Windows Installer le le prend en compteau moment de mettre à jour (c'est pouquoi j'ai mis des guillemets à "affichage").
        Cette information est stockée dans les ressources du fichier selon le format Portable Executable (PE) : c'est donc une information "Windows".
  • La granularité des composants : le même mécanisme est-il compatible à la fois pour quelques "gros composants" et une multitude de mini-composants ?
  • Les "Saveurs" : Comment fait-on gérer les notions de Debug/Release/CodeContractInside/x32/x64/AnyCpu/SL/.Net4/etc. ?
  • La définition des dépendances, par exemple :
    • "Ceci" dépend de "cela" mais seulement à partir de sa version "x".
    • "Ceci" dépend de "cela" ou "cela" (cf. l'exemple pré-cité).
  • Le "contenu" des packages :
    • certains utilisent NuGet pour distribuer du code source. Les règles applicables à la distribution de binaire (dll, exe) sont-elles toujours valides ?
    • comment choisir les fichiers à packager ? Aujourd'hui NuGet est relativement pauvre sur cet aspect.
  • Le type de dépôt de code source : Subversion offre un Revision Number fiable et pratique car c'est un entier que l'on peut mettre dans le slot Revision de la version, mais Git et d'autres n'ont pas cette notion.
  • La Continuous Integration : car gérer toutes ces versions à la main est impossible en pratique (surtout si une granularité fine est nécessaire).

Je pense avoir fait le tour (si ce n'est pas le cas, dites le moi et je complèterai). A part un aspect : les impacts du temps qui passe... indépendamment de l'augmentation des numéros de version.

La nouvelle version du package ne dépend plus de "X". Le package "Y" a été découpé en deux. "Z" s'appelle maintenant "A". "B" n'existe plus, ses features sont intégrées dans "C". "D" que l'on croyait fiable doit impérativement être mis à jour pour des raisons de sécurité. (...)

Bref, vous voyez l'idée. il y-a-t-il UNE solution ? Peut-être pas, mais il y a certainement de bonnes idées/conventions à trouver et mettre en place pour mieux gérer ces aspects. Peut-être un jour travaillerons-nous avec NuGetMore ?

 

Un peu de Sql : Union d’intervalles

On dispose d’un ensemble de ressources et de leurs disponibilités modélisées par une liste d’intervalles disjoints { BegDate , EndDate }. Concrètement, cet exercice s’applique sur une unique table :

create table dbo.tResourceAvailability
(
	ResourceID int not null,
	BegDate datetime not null,
	EndDate datetime not null,
);

Etant donné un ensemble de ResourceID (typiquement obtenu via d’autres tables et contraintes), il faut construire l’union des intervalles de disponibilité.

Cela ne semble pas excessivement compliqué. Mais ce n’est pas non plus trivial. Bref, c'est une bonne illustration d'un aspect du métier de développeur : résoudre un problème simple, avec une solution qui, si possible, l’est aussi… à condition de la trouver.

La première chose à faire en abordant un problème de ce type est de tout faire pour bien le visualiser mentalement. Un problème bien « vu » est souvent un problème résolu. En l’occurrence, il est judicieux de créer un jeu d’essai sur la base de la table ci-dessus. Un tel jeu d’essai doit être à la fois complet et simple... et parfois on peine. Ici ce n’est pas le cas : on peut se contenter de 5 ressources et de quelques créneaux répartis sur une plage d’une vingtaine « d’instants ».

La table ci-dessous présente le terrain de jeux :

  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
P0   B   E         B     E              
P1     B     E       B E                
P2   B     E           B   E       B E  
P3     B E                              
P4               B     E         B E    

Et ce que nous cherchons à obtenir est la ligne « d’union » ci-dessous :

U   B       E   B         E     B   E  

Tiens ! Tout se passe comme si l’on ne retenait que certains B ou certains E.

Tilt !... « Retenir » = « Filtrer »… = condition d’une clause where… cela semble compatible avec du Sql…

Il suffit de se poser la question : « Qu’est-ce qui fait que je garde tel B ou tel E ? ».

Prenez une minute pour regarder les deux tableaux ci-dessus…

Réponse : Les seuls B (ou E) à conserver sont ceux qui ne sont pas compris dans un autre intervalle.

On « voit » le problème à résoudre là… et on en entrevoit même la solution, il est temps de passer à l’action :

insert into dbo.tResourceAvailability( ResourceID, BegDate, EndDate )
 values( 0, '2001', '2003' ), ( 0, '2008', '2011' ),
       ( 1, '2002', '2005' ), ( 1, '2009', '2010' ),
	 ( 2, '2001', '2004' ), ( 2, '2010', '2012' ), ( 2, '2016', '2017' ),
	 ( 3, '2002', '2003' ), 
	 ( 4, '2007', '2010' ), ( 4, '2015', '2016' );

J’utilise une syntaxe d’insertion de lignes multiples standard Sql (mais disponible qu’à partir de Sql Server 2008), je ne mets que des années pour simplifier… et je procède tout doucement, étape par étape, en commençant par un select simple :

declare @T datetime = '2008';
select count(*)
	from dbo.tResourceAvailability a
	where BegDate <= @T and @T <= EndDate;

Cela calcule le nombre d’intervalles rencontrés à cette date. L’idée est la suivante (regarder le tableau) : pour un B que l’on doit conserver (par exemple 2007), le calcul donnera un (il se rencontrera lui-même), et pour un B qui ne doit pas être conservé, le calcul donnera 2 ou plus (outre lui-même, d’autres intervalles le contiennent). On progresse non ?

Oui mais… Le calcul donne 2 aussi pour 2001… car deux intervalles débutent pile sur cette date. Il est donc nécessaire d’affiner un peu. Idée : on change légèrement la condition de façon à, lorsque l’on cherche un B, ignorer les B strictement identiques à celui que l’on cherche. On enlève donc au moins un au résultat du calcul précédent, et les B que l’on garde seront ceux pour lequel on aura 0 intersection (et non plus une seule). On procède symétriquement pour E, on a donc deux requêtes légèrement différentes pour les B et les E :

declare @B datetime = '2005';
select count(*)
	from dbo.tResourceAvailability a
	where BegDate < @B and @B <= EndDate
	
declare @E datetime = '2012';
select count(*)
	from dbo.tResourceAvailability a
	where BegDate <= @E and @E < EndDate

J’ai utilisé l’opérateur count pour mieux « voir » mes requêtes, ce que je cherche à filtrer est tous les B et E qui ont un compte de 0, c’est-à-dire ceux qui n’existent pas.

select year(BegDate)
  from dbo.tResourceAvailability a
  where not exists( select * 
    from dbo.tResourceAvailability f 
    where BegDate < a.BegDate and a.BegDate <= EndDate );
Résultat :  2001  
   2001  ==> Doublon : un distinct le fera disparaitre.
   2007  
   2015  
select year(EndDate)
  from dbo.tResourceAvailability a
  where not exists( select * 
    from dbo.tResourceAvailability 
    where BegDate <= a.EndDate and a.EndDate < EndDate );
Résultat :  2005
   2012
   2017

Ce qui est presque parfait puisque nous cherchons à obtenir la liste d’intervalles suivante : 2001 – 2005, 2007 – 2012, 2015 - 2017

Nous avons le résultat final, mais « en deux morceaux ». Comment les réunir ? Avec une « union ». C’est une opération ensembliste on ne peut plus simple :

select year(BegDate)
  from dbo.tResourceAvailability a
  where not exists( select * 
    from dbo.tResourceAvailability 
    where BegDate < a.BegDate and a.BegDate <= EndDate )
union
select year(EndDate)
  from dbo.tResourceAvailability a
  where not exists( select * 
    from dbo.tResourceAvailability 
    where BegDate <= a.EndDate and a.EndDate < EndDate );
Résultat :  2001
   2005
   2007
   2012
   2015
   2017

On obtient tous les B, puis tous les E. Le doublon a disparu (2001) car c’est le comportement par défaut de l’union (pour ne pas filtrer les doublons, il faut explicitement utiliser union all). Il ne reste plus qu’à ordonner ces éléments en remarquant que l’on a nécessairement une séquence B, E, B, E, etc. Pour la rendre plus lisible, on le précise :

select 'B', year(BegDate)
  from dbo.tResourceAvailability a
  where not exists( select * 
    from dbo.tResourceAvailability 
    where BegDate < a.BegDate and a.BegDate <= EndDate )
union
select 'E', year(EndDate)
  from dbo.tResourceAvailability a
  where not exists( select * 
    from dbo.tResourceAvailability 
    where BegDate <= a.EndDate and a.EndDate < EndDate )
order by 2;
Résultat :  B  2001
   E  2005
   B  2007
   E  2012
   B  2015
   E  2017

On approche mais ce n’est pas encore idéal car on souhaite une forme de réponse similaire à celle de la donnée source : une liste d’intervalle, donc deux colonnes B et E. Transformer des lignes en colonnes, c’est le rôle de l’opérateur pivot, à condition de s’appuyer une fonction d’agrégation (count, sum, max, avg, etc.) : il ne nous est d’aucune utilité ici (en règle général, n’allez chercher l’opérateur pivot que s’il y a une agrégation possible ou souhaitable). Il nous faut trouver autre chose pour « mélanger » ces deux requêtes :

| 2001 |
  | 2012 |
  | 2001, 2005 |
| 2005 |
 x  | 2015 |
 ==>  | 2007, 2012 |
| 2007 |
  | 2017 |
  | 2015, 2017 |

Problème simple non ? Cherchez un peu…

……………

Le principe est de se servir de B comme « générateur », et de chercher la fin de l’intervalle correspondant :

select B = a.BegDate,
  	 E = << Take the first EndDate greater than a.BegDate >>
  from dbo.tResourceAvailability a
  where not exists( select * 
    from dbo.tResourceAvailability 
    where BegDate < a.BegDate and a.BegDate <= EndDate )

Ce qui revient à prendre la plus petite date supérieure à la date de début, et donne la requête finale suivante :

select distinct 
  B = BegDate,
  E = (select min(EndDate)
         from dbo.tResourceAvailability ae
	   where ae.EndDate > a.BegDate 
		and not exists( select * 
					from dbo.tResourceAvailability 
					where BegDate <= ae.EndDate 
and ae.EndDate < EndDate ))
  from dbo.tResourceAvailability a
  where not exists( select * 
			    from dbo.tResourceAvailability
			    where BegDate < a.BegDate and a.BegDate <= EndDate )

Une remarque sur les tris : Les dates obtenues peuvent être dans l’ordre croissant. C’est un hasard et il ne faut en aucun cas s’y fier  si vous voulez trier, triez explicitement (avec un order by).