tag:linuxfr.org,2005:/sections/technologieLinuxFr.org : les dépêches de Technologie2024-02-03T13:36:39+01:00/favicon.pngtag:linuxfr.org,2005:News/418082024-02-03T13:36:39+01:002024-02-03T13:36:39+01:00Pratiques dans l'industrie ferroviaire : un train de retard…Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Une histoire, comme il en est tant, hélas, de pratiques anticoncurrentielles dans l’industrie. Oubliez les imprimantes et les tracteurs, cette fois-ci, nous passons à une étape supérieure : les trains. Oui, oui, les trains, vous avez bien lu. </p>
<p><em>Si</em> les faits se confirment, un constructeur de train polonais aurait été pris en flagrant délit de pratiques anti-concurrentielles.</p>
</div><ul></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-les-faits">Les faits</a><ul>
<li><a href="#toc-pourquoi-le-train-est-tomb%C3%A9-en-panne">Pourquoi le train est tombé en panne ?</a></li>
<li><a href="#toc-v%C3%A9rifier-la-date">Vérifier la date…</a></li>
<li><a href="#toc-surprise-mat%C3%A9rielle">Surprise matérielle</a></li>
<li><a href="#toc-pas-seulement-%C3%A0-wroc%C5%82aw">Pas seulement à Wrocław</a></li>
<li><a href="#toc-la-suite">La suite</a></li>
</ul>
</li>
<li>
<a href="#toc-r%C3%A9ponse-de-loffice-des-transports-ferroviaires-utk">Réponse de l’Office des transports ferroviaires (UTK)</a><ul>
<li><a href="#toc-liens">Liens</a></li>
</ul>
</li>
</ul>
<h2 id="toc-les-faits">Les faits</h2>
<p>Au printemps 2022, le premier des onze trains du modèle <em>Newag Impuls 45WE</em>, exploités par la compagnie régionale Koleje Dolnośląskie en Basse-Silésie, est en fin de vie. Leur maintenance est gérée par la société <em>Serwis Pojazdów Szynowych</em>, (<em>SPS</em>), qui a remporté l’appel d’offre, pour un prix total d’environ 5,1 millions d’euros (22 millions de złoty). Cette inspection est obligatoire, après un million de kilomètres. Le constructeur à l’origine des trains, la société <em>Newag</em>, a également participé à l’appel d’offres, mais leur prix était supérieur d’environ 696 000 € (3 millions de złoty).</p>
<p>L’entretien d’un train est une affaire complexe : chaque pièce doit être démontée et envoyée aux fabricants concernés pour ensuite être ré-assemblées à leur retour. SPS effectue l’inspection conformément au manuel fourni, d’environ vingt mille pages. Une fois le premier train remonté, l’ordinateur de bord indique le succès de l’opération et la conformité de l’ensemble. Cependant, les onduleurs ne fournissent pas de tension aux moteurs et le train n’avance pas, sans que personne ne comprenne. Les techniciens de service recherchent, vérifient et re-vérifient les composants, parcourent les instructions – et ne trouvent aucune réponse.</p>
<p>Koleje Dolnośląskie dispose de onze trains <em>Impuls</em> et, selon le calendrier, un autre est en cours de maintenance. Tandis que le premier est toujours immobilisé, le deuxième train arrive, subit la même révision, avec malheureusement, le même résultat. Tout fonctionnait parfaitement avant la maintenance, mais les moteurs refusent de démarrer une fois celle-ci terminée. Pour empirer la situation, le constructeur refuse d’aider.</p>
<p>Nous avons maintenant deux trains à l’arrêt dans l’atelier. Le troisième rate l’inspection en raison d’une panne de batterie, et un quatrième train est envoyé à sa place. La société décide d’utiliser la quatrième pour remorquer une des locomotives en panne. Cependant, une fois connectée à l’une d’entre elle, la nouvelle locomotive s’arrête également, mais la raison semble différente et totalement inconnue. </p>
<p>Pendant ce temps, dans un autre atelier, à <em>Szczecin</em>, une autre locomotive Impuls tombe en panne, dans des circonstances très similaires : elle ne redémarre pas après la maintenance.</p>
<p>Le problème devient si grave que les médias s’en mêlent et relatent l’affaire. Les six trains les plus longs de Koleje Dolnośląskie étant hors service, les horaires doivent être réduits, des trains de remplacement doivent être envoyés, et les passagers s’entassent dans des trains trop courts. Newag explique que les trains sont bloqués par un « système de sécurité », mais rien n’est mentionné dans les pages du manuel.</p>
<p>Chaque journée d’un train immobilisé dans l’atelier coûtant plusieurs milliers de zlotys en pénalités contractuelles, et avec plusieurs trains en attente, la tension chez SPS augmente. Le problème n’étant identifié ni par les mécaniciens ni par les électriciens, quelqu’un finit rechercher des hackers polonais, et contacte le groupe <em>Dragon Sector</em>. SPS prend donc contact avec eux, dont les représentants incrédules finissent par signer le contrat. Le projet est entrepris par des membres de Dragon Sector, spécialement ceux connus pour avoir hackés le BIOS de portables Toshiba — Michał « Redford » Kowalczyk et Sergiusz « q3k » Bazański. Kuba « PanKleszcz » Stępniewicz, qui a de l’expérience dans l’automatisation industrielle, se joint également à l’équipe.</p>
<p>L’équipe se met rapidement au travail et Kuba part en voyage à l’atelier. Une fois sur place, ils reçoivent un train qui ne roule pas, deux ordinateurs de rechange et les fichiers SDK du fabricant de l’ordinateur. Ils commencent par écouter le bus de données CAN (Controller Area Network), mais sans information sur les protocoles, la tâche s’avère ardue. Ils tentent de télécharger le microcode de l’ordinateur de bord, mais la documentation du SDK permet uniquement les mises à jour, pas d’inspecter la version installée.</p>
<p>La première tentative d’utilisation d’une ancienne version du micrologiciel, sur un ordinateur de rechange se solde par un échec : l’ordinateur ne répond plus. Ils trouvent finalement l’interface de débogage sur le dernier ordinateur de rechange, et octet par octet, en extraient la mémoire. L’ordinateur est basé sur l’architecture Infineon TriCore, souvent utilisée dans l’industrie automobile, et les chercheurs finissent enfin par examiner le code en utilisant une version modifiée de Ghidra.</p>
<p>Les travaux avancent doucement, mais l’échéance approche et les trains tombent toujours en panne. Dos au mur, Koleje Dolnośląskie, décide de coopérer avec Newag sur l’entretien des trains en panne, y compris ceux qui, selon l’appel d’offres initial, devaient être entretenus uniquement par SPS. Le contrat devant être résilié d’ici une semaine, les chercheurs se mettent à travailler de plus belle.</p>
<p>Le groupe est enfin en possession de tous les micro-logiciels des trains, autant ceux en état de marche que ceux en panne. Chacun des trains ayant des fonctionnalités particulières et une version différente du logiciel, l’analyse est difficile, mais ils commencent à identifier une piste. Entre un ordinateur en état de marche et un autre en panne, certaines plages de mémoire diffèrent. Une fois corrigées sur un ordinateur en panne, celui-ci démarre enfin. Le test étant effectué sur un ordinateur isolé sur un bureau, il s’arrête dès que le logiciel détecte qu’il manque le reste du train, mais il est prêt à faire fonctionner les onduleurs.</p>
<p>Moins de 24 heures avant la date fatidique, les chercheurs identifient des paramètres supplémentaires pour faire démarrer le train. Malheureusement, le condensateur du dernier ordinateur de bord en état de marche brûle pendant les expériences. Après un nouveau brainstorming et de nombreuses tentatives pour combiner deux ordinateurs endommagés en un seul, le premier est réparé, et à 2 heures du matin, la veille de l’heure apocalyptique, les chercheurs configurent l’ordinateur qui fera démarrer le train.</p>
<p>Un de nos héros monte à bord d’un train (d’une autre compagnie) pour rejoindre l’atelier avec un ordinateur probablement en état de marche devant les représentants des chemins de fer de Basse-Silésie, qui ont annoncé leur visite pour 9h30. Malheureusement, le train que prend le chercheur pour se rendre sur place est en retard. Finalement, dans la matinée, un chercheur équipé d’un ordinateur arrive sur les lieux et le connecte au train en panne. Et celui-ci ne bouge pas… Un autre brainstorming identifie le dernier drapeau oublié et à 8h42 le train parvient enfin à démarrer !</p>
<p>La délégation de Koleje Dolnośląskie, voyant à 9h30 que les trains ont une chance de reprendre vie, ne résilie pas le contrat avec SPS.</p>
<h3 id="toc-pourquoi-le-train-est-tombé-en-panne">Pourquoi le train est tombé en panne ?</h3>
<p>Déterminer comment faire démarrer le train ne représentait qu’une petite partie du problème. Il fallait maintenant découvrir pourquoi il était tombé en panne. </p>
<p>Des mois d’analyse et de rétro-ingénierie ont permis de révéler des conditions extrêmement intéressantes inscrites dans le code logiciel de différents trains fournis par Newag. Après des centaines d’heures passées à étudier les codes émis par des dizaines de trains, il a été possible d’identifier des mécanismes provoquant des pannes soudaines dans les trains. </p>
<p>Les valeurs numériques 53,13845 et 17,99011 trouvées dans le code informatique semblaient familières à première vue. Il s’est rapidement avéré qu’il s’agissait de coordonnées GPS, indiquant les environs de la gare de Bydgoszcz Główna, ou, plus précisément, le centre de service PESA situé juste à côté. Bientôt, les coordonnées d’autres services susceptibles d’effectuer des réparations et des inspections de trains en Pologne ont également été trouvées. Ci-dessous, nous montrons le pseudo-code de l’algorithme : </p>
<pre><code class="c"><span class="n">check1</span> <span class="o">=</span> <span class="mf">53.13845</span> <span class="o"><</span> <span class="n">lat</span> <span class="o">&&</span> <span class="n">lat</span> <span class="o"><</span> <span class="mf">53.13882</span> <span class="o">&&</span> <span class="mf">17.99011</span> <span class="o"><</span> <span class="kt">long</span> <span class="o">&&</span> <span class="kt">long</span> <span class="o"><</span> <span class="mf">17.99837</span><span class="err"> </span><span class="p">;</span>
<span class="n">check2</span> <span class="o">=</span> <span class="mf">53.14453</span> <span class="o"><</span> <span class="n">lat</span> <span class="o">&&</span> <span class="n">lat</span> <span class="o"><</span> <span class="mf">53.14828</span> <span class="o">&&</span> <span class="mf">18.00428</span> <span class="o"><</span> <span class="kt">long</span> <span class="o">&&</span> <span class="kt">long</span> <span class="o"><</span> <span class="mf">18.00555</span><span class="err"> </span><span class="p">;</span>
<span class="n">check3</span> <span class="o">=</span> <span class="mf">52.17048</span> <span class="o"><</span> <span class="n">lat</span> <span class="o">&&</span> <span class="n">lat</span> <span class="o"><</span> <span class="mf">52.17736</span> <span class="o">&&</span> <span class="mf">21.53480</span> <span class="o"><</span> <span class="kt">long</span> <span class="o">&&</span> <span class="kt">long</span> <span class="o"><</span> <span class="mf">21.54437</span><span class="err"> </span><span class="p">;</span>
<span class="n">check4</span> <span class="o">=</span> <span class="mf">49.60336</span> <span class="o"><</span> <span class="n">lat</span> <span class="o">&&</span> <span class="n">lat</span> <span class="o"><</span> <span class="mf">49.60686</span> <span class="o">&&</span> <span class="mf">20.70073</span> <span class="o"><</span> <span class="kt">long</span> <span class="o">&&</span> <span class="kt">long</span> <span class="o"><</span> <span class="mf">20.70840</span>
<span class="o">&&</span> <span class="p">(</span><span class="n">this</span><span class="o">-></span><span class="n">lock_function_test</span> <span class="o">&</span> <span class="mi">1</span><span class="p">)</span><span class="err"> </span><span class="p">;</span>
<span class="n">check5</span> <span class="o">=</span> <span class="mf">53.10244</span> <span class="o"><</span> <span class="n">lat</span> <span class="o">&&</span> <span class="n">lat</span> <span class="o"><</span> <span class="mf">53.10406</span> <span class="o">&&</span> <span class="mf">18.07817</span> <span class="o"><</span> <span class="kt">long</span> <span class="o">&&</span> <span class="kt">long</span> <span class="o"><</span> <span class="mf">18.08243</span><span class="err"> </span><span class="p">;</span>
<span class="n">check6</span> <span class="o">=</span> <span class="mf">50.12608</span> <span class="o"><</span> <span class="n">lat</span> <span class="o">&&</span> <span class="n">lat</span> <span class="o"><</span> <span class="mf">50.12830</span> <span class="o">&&</span> <span class="mf">19.38411</span> <span class="o"><</span> <span class="kt">long</span> <span class="o">&&</span> <span class="kt">long</span> <span class="o"><</span> <span class="mf">19.38872</span><span class="err"> </span><span class="p">;</span>
<span class="n">check7</span> <span class="o">=</span> <span class="mf">52.77292</span> <span class="o"><</span> <span class="n">lat</span> <span class="o">&&</span> <span class="n">lat</span> <span class="o"><</span> <span class="mf">52.77551</span> <span class="o">&&</span> <span class="mf">18.22117</span> <span class="o"><</span> <span class="kt">long</span> <span class="o">&&</span> <span class="kt">long</span> <span class="o"><</span> <span class="mf">18.22724</span><span class="err"> </span><span class="p">;</span></code></pre>
<p>Les paires de coordonnées définissent les zones d’atelier. Il existe une condition inscrite dans le code informatique qui exige que le train soit désactivé s’il passe au moins dix jours dans l’un de ces ateliers. L’un des ateliers appartient à Newag lui-même - mais une condition logique différente a été définie pour ses coordonnées, probablement à des fins de test. </p>
<p>D’autres surprises furent bientôt découvertes. Il s’agissait notamment du blocage du train lorsqu’un de ses composants (vérifié par son numéro de série) était remplacé. Une option permettant d’annuler le verrouillage a également été découverte — cela ne nécessitait pas de définir des indicateurs au niveau de la mémoire de l’ordinateur, mais uniquement la séquence appropriée de clics sur les boutons dans la cabine et sur l’écran de l’ordinateur de bord. </p>
<p>Lorsque les informations sur le lancement réussi des trains Impuls sont parvenues aux médias, les trains ont reçu une mise à jour logicielle qui supprimait cette possibilité de « réparation ». Un code a été trouvé sur un autre train lui indiquant de « casser » après avoir parcouru un million de kilomètres. </p>
<h3 id="toc-vérifier-la-date">Vérifier la date…</h3>
<p>Une situation assez cocasse a été rencontrée dans une autre escouade qui a refusé de démarrer, le 21 novembre 2022, alors qu’elle n’était pas sur place à ce moment-là. L’ordinateur signale une panne du compresseur, les mécaniciens n’ont, pourtant, identifié aucune panne sur le compresseur. Les <a href="https://fr.wikipedia.org/wiki/Pantographe_(transport)">pantographes</a> ne fonctionnant toujours pas, l’analyse du code informatique a détecté une condition de crash suivante, qui signalait une panne de compresseur :</p>
<pre><code class="txt"><span class="err">si</span> <span class="err">le</span> <span class="err">jour</span> <span class="err">est</span> <span class="err">supérieur</span> <span class="err">ou</span> <span class="err">égal</span> <span class="err">au</span> <span class="err">21</span> <span class="err">et</span>
<span class="err">si</span> <span class="err">le</span> <span class="err">mois</span> <span class="err">est</span> <span class="err">supérieur</span> <span class="err">ou</span> <span class="err">égal</span> <span class="err">à</span> <span class="err">11</span> <span class="err">et</span>
<span class="err">si</span> <span class="err">l’année</span> <span class="err">est</span> <span class="err">supérieure</span> <span class="err">ou</span> <span class="err">égale</span> <span class="err">à</span> <span class="err">2021</span></code></pre>
<p>La situation était amusante, car le train devait être inspecté en novembre 2021 (un an avant la panne), mais par coïncidence, la condition n’a pas fonctionné. Le train a été entretenu un instant plus tôt et n’a été relancé qu’en janvier 2022 - et cette date ne répondait plus à la condition logique sophistiquée décrite ci-dessus. C’est probablement l’incapacité de l’auteur du logiciel à écrire correctement des conditions qui a obligé à attendre le 21 novembre 2022 pour que l’on puisse constater l’effet prévu.</p>
<h3 id="toc-surprise-matérielle">Surprise matérielle</h3>
<p>Les surprises ne se cachent pas seulement dans les logiciels informatiques. Dans l’un des dépôts, les chercheurs ont découvert un dispositif signé comme « convertisseur UDP / CAN », permettant vraisemblablement une communication à distance avec le train. Le supprimer n’a pas empêché quoi que ce soit de fonctionner. L’analyse a montré que l’ordinateur de bord envoyait des informations sur l’état du verrouillage à cet appareil et que l’appareil lui-même était connecté à un modem GSM. </p>
<h3 id="toc-pas-seulement-à-wrocław">Pas seulement à Wrocław</h3>
<p>L’information selon laquelle le service SPS avait réussi à réparer les trains Newag « cassés » est rapidement parvenue à d’autres services. Cela s’est avéré être un problème assez courant. À Wrocław, ils ont analysé 13 rames Impuls, mais celles qui circulaient à Koleje Mazowieckie sont également tombées en panne (une unité), deux à Opole, quatre à Cracovie, une à Zielona Góra, quatre à Szczecin et une à SKM. Heureusement, chacune a été réparée à l’aide d’un outil développé par nos chercheurs, qui supprime les verrous logiciels de l’ordinateur de bord. Au total, nos collègues ont analysé le logiciel de 29 trains, et seulement cinq ont trouvé des surprises allant au-delà des instructions d’exploitation officielles. </p>
<h3 id="toc-la-suite">La suite</h3>
<p>Nous laissons l’évaluation des solutions utilisées par le fabricant aux lecteurs et clients de cette entreprise. Il est intéressant de noter que, même si des poursuites judiciaires sont en cours, il est difficile de trouver en Pologne une institution qui ferait autre chose qu’exprimer un intérêt amical dans cette affaire. </p>
<p>Nous n’avons connaissance d’aucune mesure prise par « l’Office de la protection des consommateurs et de la concurrence », ni par « l’Office du transport ferroviaire », qui semble pourtant appropriée. Cette dernière à pour rôle de surveiller les pratiques des sociétés qui travaillent avec les organisations gouvernementales locales. Que des voyageurs aient subi des désagréments ou forcés à utiliser des transports alternatifs pendant des mois semble pourtant éligible à un dédommagement. </p>
<p>La seule institution connue à avoir pris des mesures est le CERT Polska, qui a été informé de la découverte par les chercheurs. Le commentaire que nous avons reçu montre que CERT Polska a informé les « autorités compétentes » et que l’affaire est traitée par les autorités chargées de l’application de la loi. </p>
<p>Nous félicitons les meilleurs hackers polonais pour leur découverte intéressante et l’exécution professionnelle de la commande. Décidément rien n’est plus motivant qu’une date limite demain matin. </p>
<p>L’article ci-dessus n’est qu’un bref résumé de la présentation donnée lors de la conférence <em>Oh My H@ck</em> le 5 décembre 2023 par les membres de l’équipe : Jakub Stępniewicz, Sergiusz Bazański et Michał Kowalczyk. L’article a omis de nombreux détails et une grande partie technique de l’analyse — nous ne pouvons qu’espérer que cela motivera les auteurs de l’étude à rédiger et publier son cours. Mis à jour le 05/12/2023 à 16h00 </p>
<h2 id="toc-réponse-de-loffice-des-transports-ferroviaires-utk">Réponse de l’Office des transports ferroviaires (UTK)</h2>
<p>Nous avons reçu la position officielle de l’Office des transports ferroviaires (UTK), que nous citons intégralement ci-dessous : </p>
<blockquote>
<p>Le président de l’UTK est au courant de l’affaire et a vérifié les informations concernant les analyses effectuées sur les logiciels des véhicules ferroviaires et coopère également avec les services compétents à ce sujet. En collaboration avec CERT Polska (une équipe créée pour répondre aux incidents violant la sécurité Internet), une réunion avec le constructeur ferroviaire a été organisée. Les véhicules répondent aux exigences essentielles précisées dans les dispositions des directives européennes. C’est la personne qui commande le véhicule qui détermine les conditions de service et de garantie dans le cadre de la liberté contractuelle. Ces exigences sont incluses dans les contrats d’achat de trains. Toute limitation des capacités de service, y compris les limitations introduites dans le logiciel, peut constituer un éventuel litige civil entre le client et le fabricant. Le président de l’UTK n’est pas l’autorité compétente en la matière. </p>
<p>Conformément à l’art. 41 alinéa 2 de la loi du 5 juillet 2018 relative au système national de cybersécurité (texte consolidé : Journal des lois de 2023, articles 913, 1703), l’autorité chargée de la cybersécurité du secteur des transports (hors sous-secteur du transport par eau) est l’autorité compétente en matière de cybersécurité. <br>
Ministre chargé des questions de transports.</p>
</blockquote>
<h3 id="toc-liens">Liens</h3>
<ul>
<li>
<a href="https://pl.wikipedia.org/wiki/Newag_Impuls">Newag_Impuls</a> (pl)</li>
<li>
<a href="https://en.wikipedia.org/wiki/Infineon_TriCore">Infineon_TriCore</a> (en)</li>
<li>
<a href="https://en.wikipedia.org/wiki/Ghidra">Ghidra</a> (en)</li>
<li>
<a href="https://en.wikipedia.org/wiki/CERT_Polska">CERT Polska</a> (en)</li>
<li><a href="//linuxfr.org/users/chocolatineflying/journaux/devoir-hacker-un-train">journal LinuxFr.org</a></li>
<li>BadCyber <a href="https://badcyber.com/dieselgate-but-for-trains-some-heavyweight-hardware-hacking/">Dieselgate, but for trains – some heavyweight hardware hacking</a> (en)</li>
<li>404media <a href="https://www.404media.co/polish-hackers-repaired-trains-the-manufacturer-artificially-bricked-now-the-train-company-is-threatening-them/">Polish Hackers Repaired Trains the Manufacturer Artificially Bricked. Now The Train company is threatening them</a> (en)</li>
<li>
<a href="https://social.hackerspace.pl/@q3k/111528162462505087">l’histoire par un ses hackers</a> (en)</li>
<li>HackADay <a href="https://hackaday.com/2023/12/06/the-deere-disease-spreads-to-trains/">The Deere Disease Spreads To Trains</a> (en)</li>
</ul>
</div><div><a href="https://linuxfr.org/news/pratiques-dans-l-industrie-ferroviaire-un-train-de-retard.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/134228/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/pratiques-dans-l-industrie-ferroviaire-un-train-de-retard#comments">ouvrir dans le navigateur</a>
</p>
Andre RodierYsabeau 🧶 🧦BAudL'intendant zonardmartoniBenoît SibaudFrancoisA30bobble bubblehttps://linuxfr.org/nodes/134228/comments.atomtag:linuxfr.org,2005:News/417272023-10-19T17:18:29+02:002023-10-19T17:18:29+02:00Open Research Webinar Series du 7 novembre 2023 avec CryptPad et TRISTANLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Proposée et co-organisée par OW2 et la Fondation Eclipse depuis décembre 2020, la série de webinaires « <em>Open Research Webinars</em> » présente des innovations open source basées sur des technologies de pointe, qui contribuent à façonner l’avenir des logiciels open source et de l’industrie informatique.</p>
<p>Ces webinaires donnent la parole aux chercheurs développant des projets open source au sein des programmes européens de recherche financés par des fonds publics ou encore aux industriels exploitant des projets issus de la recherche. Le prochain webinaire a lieu le mardi 7 novembre 2023 de 16h à 17h. Toutes les présentations seront en anglais. </p>
<p>Programme du 7 novembre 2023 :</p>
<ul>
<li>
<a href="https://cryptpad.org/">CryptPad</a> : Suite office collaborative chiffrée de bout en bout, par David Benqué de XWiki ;</li>
<li>
<a href="https://cordis.europa.eu/project/id/101095947">TRISTAN</a> : Together for RISc-V Technology and ApplicatioNs, par Rob Wullems de NXP.</li>
</ul>
<p>L’inscription est gratuite et obligatoire (le lien de connexion sera envoyé sur inscription). </p>
</div><ul><li>lien nᵒ 1 : <a title="https://opensourceinnovation.eu/" hreflang="en" href="https://linuxfr.org/redirect/112850">Site Open Research Webinar</a></li><li>lien nᵒ 2 : <a title="https://www.eventbrite.fr/e/open-research-webinar-november-7-tickets-718514866307" hreflang="en" href="https://linuxfr.org/redirect/112851">Inscription au webinaire</a></li></ul><div><p><img src="//img.linuxfr.org/img/68747470733a2f2f7777772e6f77322e6f72672f646f776e6c6f61642f4e6577736c6574746572732f53657074656d626572323032334e6577736c65747465722f536f6369616c436172645f4c6f676f50726573656e746174696f6e253238322532392e6a7067/SocialCard_LogoPresentation%282%29.jpg" alt="Titre de l'image" title="Source : https://www.ow2.org/download/Newsletters/September2023Newsletter/SocialCard_LogoPresentation%282%29.jpg"></p>
</div><div><a href="https://linuxfr.org/news/open-research-webinar-series-du-7-novembre-2023-avec-cryptpad-et-tristan.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/133679/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/open-research-webinar-series-du-7-novembre-2023-avec-cryptpad-et-tristan#comments">ouvrir dans le navigateur</a>
</p>
nuelBenoît SibaudFlorent ZaraNÿcohttps://linuxfr.org/nodes/133679/comments.atomtag:linuxfr.org,2005:News/415632023-07-04T09:28:24+02:002023-07-04T10:57:44+02:00Écrire une appli web en une journée avec SQLPageLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Aujourd'hui, je souhaite vous présenter le logiciel <a href="https://sql.ophir.dev">SQLPage</a>, un outil open-source (MIT) qui permet de développer des applications web complètes, avec une belle interface graphique et une base de données, entièrement en SQL.</p>
<p>Le SQL est un langage très simple, qui permet de faire des recherches dans des base de données. Il est utilisé depuis les années 80, et est encore omniprésent aujourd'hui. Contrairement aux langages de programmation traditionnels, on peut apprendre les bases de SQL en une journée, et commencer à faire des requêtes complexes croisant plusieurs tables de données très rapidement. </p>
<p>Dans une application web traditionnelle, on développe aujourd'hui en général trois composants :</p>
<ul>
<li>un <em>front-end</em>, qui gère uniquement l'interface utilisateur,</li>
<li>un <em>back-end</em>, qui traite les requêtes du <em>front-end</em> et contient le cœur de la logique de l'application lorsque celle-ci est complexe,</li>
<li>une <em>base de données</em> qui va stocker et structurer les données, s'assurant de leur cohérence et de leur bonne organisation. </li>
</ul>
<p>Les deux premiers éléments sont en général ceux sur lesquels les programmeurs passent le plus de temps lors du développement d'une application. Et pourtant, c'est souvent le dernier, la base de données, qui contient la substantifique moelle de l'application !</p>
<p>Ce que propose SQLPage, c'est de s'abstraire complètement du <em>back-end</em> et du <em>front-end</em>, et générer toute une application entièrement en SQL. Nous allons voir ici comment c'est possible, avec un exemple concret d'application que nous allons construire ensemble en SQL : à la Tricount.com, une petite application qui permet de gérer ses comptes entre amis.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://sql.ophir.dev" hreflang="en" href="https://linuxfr.org/redirect/112360">Site officiel du projet SQLPage</a></li><li>lien nᵒ 2 : <a title="https://github.com/lovasoa/sqlpage" hreflang="en" href="https://linuxfr.org/redirect/112361">Code source du projet sur Github </a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-est-ce-de-la-sorcellerie">Est-ce de la sorcellerie ?</a></li>
<li><a href="#toc-comment-%C3%A7a-marche">Comment ça marche ?</a></li>
<li>
<a href="#toc-construisons-une-application">Construisons une application</a><ul>
<li>
<a href="#toc-notre-application--une-application-opensource-pour-faire-ses-comptes-entre-amis">Notre application : une application opensource pour faire ses comptes entre amis</a><ul>
<li><a href="#toc-premi%C3%A8re-%C3%A9tape--choisir-un-sch%C3%A9ma-pour-notre-base-de-donn%C3%A9es">Première étape : choisir un schéma pour notre base de données</a></li>
<li><a href="#toc-deuxi%C3%A8me-%C3%A9tape--cr%C3%A9ation-de-la-base-de-donn%C3%A9es-et-lancement-de-sqlpage">Deuxième étape : création de la base de données et lancement de SQLPage</a></li>
<li><a href="#toc-troisi%C3%A8me-%C3%A9tape--cr%C3%A9ation-de-notre-premi%C3%A8re-page-web">Troisième étape : création de notre première page web</a></li>
<li><a href="#toc-insertion-de-donn%C3%A9es-dans-la-base-de-donn%C3%A9es">Insertion de données dans la base de données</a></li>
</ul>
</li>
<li>
<a href="#toc-am%C3%A9lioration-de-lapplication-cr%C3%A9ation-de-nouvelles-pages">Amélioration de l'application, création de nouvelles pages</a><ul>
<li><a href="#toc-cerise-sur-le-g%C3%A2teau--calcul-des-dettes">Cerise sur le gâteau : calcul des dettes</a></li>
</ul>
</li>
</ul>
</li>
<li>
<a href="#toc-conclusion">Conclusion</a><ul>
<li>
<ul>
<li><a href="#toc-pour-r%C3%A9sumer-ce-que-nous-avons-vu">Pour résumer ce que nous avons vu</a></li>
<li><a href="#toc-pour-aller-plus-loin">Pour aller plus loin</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="toc-est-ce-de-la-sorcellerie">Est-ce de la sorcellerie ?</h2>
<p>Tout d'abord, mettons les choses au clair : votre application aura bien un backend et un frontend, il n'y a pas de miracle. Mais pour les applications simples, le frontend est souvent juste un assemblage de composants standards, et le backend qu'une sorte de passe-plats entre le frontend et la base de données. Ce que permet SQLPage, et que nous allons étudier ici c'est :</p>
<ul>
<li>d'invoquer des composants prédéfinis d'interface graphique en donnant simplement leur nom et quelques paramètres,</li>
<li>de faire le lien entre l'interface graphique et la base de données avec de simples fichiers SQL qui sont exécutés automatiquement lorsque l'utilisateur charge une page. </li>
</ul>
<h2 id="toc-comment-ça-marche">Comment ça marche ?</h2>
<p>SQLPage est un simple serveur web : c'est un programme qui tourne en continu, attend des requêtes HTTP, et dès qu'il en reçoit une, fournit une réponse.</p>
<p>Si SQLPage reçoit une requête vers <code>/site/contenu.sql?article=42</code>, il va chercher un fichier nommé <code>contenu.sql</code>, dans un dossier nommé <code>site</code>. Il va ensuite lire le contenu du fichier, et l'interpréter comme une série de requêtes SQL, qui vont être préparées. Elles seront ensuite exécutées une par une. Si l'une de ces requêtes fait référence à une variable nommée <code>$article</code>, la valeur <code>42</code> venant de la requête de l'utilisateur lui sera associée.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6c6f7661736f612f53514c706167652f626c6f622f6d61696e2f646f63732f6172636869746563747572652e706e673f7261773d74727565/architecture.png?raw=true" alt="architecture sqlpage" title="Source : https://github.com/lovasoa/SQLpage/blob/main/docs/architecture.png?raw=true"></p>
<p>Les requêtes sont envoyées à la base de données, et celle-ci commence à retourner des lignes de données, une par une.</p>
<p>Les lignes vont ensuite être analysées <em>au fil de l'eau</em> par SQLPage, qui va décider quel composant graphique renvoyer au navigateur web, et quelles données utiliser pour remplir le composant. </p>
<h2 id="toc-construisons-une-application">Construisons une application</h2>
<p>Pour rendre tout ce discours plus concret, créons ensemble une petite application, entièrement en SQL, et en vingt minutes. </p>
<p>Pour vous donner un avant-goût, voilà ce à quoi nous allons arriver au final</p>
<table>
<thead>
<tr>
<th>Page d'accueil</th>
<th>Gestion d'utilisateurs</th>
<th>Liste de dépenses</th>
<th>Graphique de dettes</th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6c6f7661736f612f53514c706167652f6173736574732f3535323632392f33373762326161632d656332372d343238322d383962392d626432366664373737643961/377b2aac-ec27-4282-89b9-bd26fd777d9a" alt="image" title="Source : https://github.com/lovasoa/SQLpage/assets/552629/377b2aac-ec27-4282-89b9-bd26fd777d9a"></td>
<td><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6c6f7661736f612f53514c706167652f6173736574732f3535323632392f37613564343666312d666564382d346532392d613862362d336638633139656136336165/7a5d46f1-fed8-4e29-a8b6-3f8c19ea63ae" alt="image" title="Source : https://github.com/lovasoa/SQLpage/assets/552629/7a5d46f1-fed8-4e29-a8b6-3f8c19ea63ae"></td>
<td><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6c6f7661736f612f53514c706167652f6173736574732f3535323632392f33646339306138352d613563322d343464392d616133332d646265383634373061653339/3dc90a85-a5c2-44d9-aa33-dbe86470ae39" alt="image" title="Source : https://github.com/lovasoa/SQLpage/assets/552629/3dc90a85-a5c2-44d9-aa33-dbe86470ae39"></td>
<td><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6c6f7661736f612f53514c706167652f6173736574732f3535323632392f31613866623865342d336461642d343030612d626535342d656235623534656339303162/1a8fb8e4-3dad-400a-be54-eb5b54ec901b" alt="image" title="Source : https://github.com/lovasoa/SQLpage/assets/552629/1a8fb8e4-3dad-400a-be54-eb5b54ec901b"></td>
</tr>
</tbody>
</table>
<p>Il n'y a pas toutes les fonctionnalités de l'application originelle, mais c'est seulement 83 lignes de code, grâce à tout ce que SQLPage gère automatiquement. Et le résultat est quand même plus joli que l'original.</p>
<h3 id="toc-notre-application--une-application-opensource-pour-faire-ses-comptes-entre-amis">Notre application : une application opensource pour faire ses comptes entre amis</h3>
<p>Nous allons créer une application pour faire ses comptes entre amis. Elle aura les fonctionnalités suivantes :</p>
<ul>
<li>créer un nouveau compte de dépenses partagé</li>
<li>ajouter des participants et visualiser la liste des participants existants</li>
<li>pour chaque participant :
<ul>
<li>ajouter une dépense</li>
<li>voir les dépenses des autres</li>
<li>voir combien il doit au reste du groupe ou combien lui est dû</li>
</ul>
</li>
</ul>
<h4 id="toc-première-étape--choisir-un-schéma-pour-notre-base-de-données">Première étape : choisir un schéma pour notre base de données</h4>
<p>Et oui, on ne va pas passer quatre jours à choisir un framework JavaScript, un framework CSS, un ORM, ou autres choses compliquées que l'on fait quand on commence une application web classique. Avec SQLPage, on rentre tout de suite dans le cœur du sujet, et ce qui sera important pour la suite: quelles données stockerons-nous, et sous quelle forme. </p>
<p>Ici, je propose le schéma suivant :</p>
<ul>
<li>une table <code>expense_group</code> pour nos comptes de dépenses partagés, avec un identifiant numérique et un nom. </li>
<li>une table <code>group_member</code> pour les utilisateurs, avec un identifiant numérique, un nom, et l'identifiant du compte partagé auquel il appartient. </li>
<li>une table <code>expense</code> pour les dépenses, avec l'identifiant de l'utilisateur ayant fait la dépense, une description, et un montant. Pour cet exemple, nous ne prendrons pas en compte le cas où une dépense peut ne concerner qu'une partie du groupe; ce sera simple à ajouter dans un second temps. </li>
</ul>
<h4 id="toc-deuxième-étape--création-de-la-base-de-données-et-lancement-de-sqlpage">Deuxième étape : création de la base de données et lancement de SQLPage</h4>
<p>C'est parti ! <a href="https://github.com/lovasoa/SQLpage/releases">Téléchargeons SQLPage sur le site officiel</a>. </p>
<p>Créons un dossier pour notre application, et dans ce dossier créons la structure de fichiers suivante:</p>
<pre><code>├── sqlpage
│ ├── migrations
│ │ └── 000_base.sql
│ └── sqlpage.json
└── sqlpage.bin
</code></pre>
<p>Nous créons donc les fichiers suivants:</p>
<ul>
<li>
<code>sqlpage/migrations/000_base.sql</code> dans lequel nous définirons la structure de notre base de données</li>
<li>
<code>sqlpage/sqlpage.json</code> dans lequel nous mettrons pour l'instant simplement la ligne suivante: <code>{"database_url": "sqlite://:memory:"}</code>. Cela nous permet de travailler avec une base de données temporaire en mémoire. Nous le modifierons plus tard pour nous connecter à une base de données plus pérenne.</li>
</ul>
<p>Intéressons-nous d'abord à <code>sqlpage/migrations/000_base.sql</code>. Pour créer la structure de base de données définie plus tôt, utilisons quelques <a href="https://www.sqlite.org/lang_createtable.html">instructions de création de table</a> :</p>
<pre><code class="sql"><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">expense_group</span><span class="p">(</span>
<span class="n">id</span> <span class="nb">INTEGER</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTOINCREMENT</span><span class="p">,</span>
<span class="n">name</span> <span class="nb">TEXT</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">group_member</span><span class="p">(</span>
<span class="n">id</span> <span class="nb">INTEGER</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTOINCREMENT</span><span class="p">,</span>
<span class="n">group_id</span> <span class="nb">INTEGER</span> <span class="k">REFERENCES</span> <span class="n">expense_group</span><span class="p">(</span><span class="n">id</span><span class="p">),</span>
<span class="n">name</span> <span class="nb">TEXT</span>
<span class="p">);</span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">expense</span><span class="p">(</span>
<span class="n">id</span> <span class="nb">INTEGER</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="n">AUTOINCREMENT</span><span class="p">,</span>
<span class="n">spent_by</span> <span class="nb">INTEGER</span> <span class="k">REFERENCES</span> <span class="n">group_member</span><span class="p">(</span><span class="n">id</span><span class="p">),</span> <span class="c1">-- identifiant du membre qui a fait la dépense</span>
<span class="nb">date</span> <span class="k">TIMESTAMP</span> <span class="k">DEFAULT</span> <span class="k">CURRENT_TIMESTAMP</span><span class="p">,</span> <span class="c1">-- date et heure de la dépense</span>
<span class="n">name</span> <span class="nb">TEXT</span><span class="p">,</span> <span class="c1">-- intitulé</span>
<span class="n">amount</span> <span class="nb">DECIMAL</span> <span class="c1">-- montant en euros</span>
<span class="p">);</span></code></pre>
<p>On peut maintenant lancer l'exécutable <code>sqlpage.bin</code> (ou <code>sqlpage.exe</code> sous Windows 😬) depuis le dossier de notre site.</p>
<p>Il doit se lancer, et afficher dans le terminal le message suivant : <code>Applying migrations from 'sqlpage/migrations [...] Found 1 migrations</code>. Cela signifie qu'il a créé avec succès notre base de données selon le schéma demandé.</p>
<p>En ouvrant la page <code>http://localhost:8080</code> sur notre navigateur web, nous devrions voir le message suivant:</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6c6f7661736f612f53514c706167652f6173736574732f3535323632392f34616533643165372d303366322d343230302d616537632d373061636134303537346238/4ae3d1e7-03f2-4200-ae7c-70aca40574b8" alt="Screenshot 2023-06-28 at 16-46-40 SQLpage" title="Source : https://github.com/lovasoa/SQLpage/assets/552629/4ae3d1e7-03f2-4200-ae7c-70aca40574b8"></p>
<h4 id="toc-troisième-étape--création-de-notre-première-page-web">Troisième étape : création de notre première page web</h4>
<p>Le moment tant attendu est arrivé : nous allons créer notre première page web et pouvoir l'ouvrir dans notre navigateur.</p>
<p>Pour cela, créons un fichier nommé <code>index.sql</code> à la racine du dossier de notre site web. À l'intérieur, nous allons écrire une série de requêtes SQL.</p>
<p>SQLPage marche de la manière suivante : on fait une première requête pour invoquer un composant graphique, comme une liste, un formulaire, du texte, ou un graphique. Ensuite, on fait une seconde requête pour définir comment peupler notre composant : les éléments de la liste, les champs du formulaire, les paragraphes de texte, ou les points de notre graphique.</p>
<p>Dans notre cas, notre premier composant sera un formulaire pour créer un nouveau groupe de dépenses à partager entre amis. Pour cela, nous allons invoquer le composant <a href="https://sql.ophir.dev/documentation.sql?component=form#component"><code>form</code></a>. Dans <code>index.sql</code>, écrivons :</p>
<pre><code class="sql"><span class="k">SELECT</span>
<span class="s1">'form'</span> <span class="k">as</span> <span class="n">component</span><span class="p">,</span>
<span class="s1">'Nouveau compte partagé'</span> <span class="k">as</span> <span class="n">title</span><span class="p">,</span>
<span class="s1">'Créer le compte de dépenses partagé !'</span> <span class="k">as</span> <span class="n">validate</span><span class="p">;</span></code></pre>
<p>Cela crée un formulaire, vide, que l'on peut déjà voir dans notre navigateur ! Maintenant, ajoutons un champ dans le formulaire. Immédiatement à la suite de la requête précédente, ajoutons:</p>
<pre><code class="sql"><span class="k">SELECT</span> <span class="s1">'Nom du compte'</span> <span class="k">AS</span> <span class="n">label</span><span class="p">,</span> <span class="s1">'shared_expense_name'</span> <span class="k">AS</span> <span class="n">name</span><span class="p">;</span></code></pre>
<p>Rouvrons notre navigateur, et nous devrions maintenant voir cela :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6c6f7661736f612f53514c706167652f6173736574732f3535323632392f34643333636562302d343830642d346633342d616465362d356561666333303233383839/4d33ceb0-480d-4f34-ade6-5eafc3023889" alt="sqlpage form" title="Source : https://github.com/lovasoa/SQLpage/assets/552629/4d33ceb0-480d-4f34-ade6-5eafc3023889"></p>
<h4 id="toc-insertion-de-données-dans-la-base-de-données">Insertion de données dans la base de données</h4>
<p>Pour l'instant, lorsque l'on clique sur le bouton <em>Créer le compte de dépenses partagées</em>, il ne se passe rien. Corrigeons cela !</p>
<p>Toujours dans <code>index.sql</code>, à la fin de notre fichier, ajoutons une nouvelle requête SQL :</p>
<pre><code class="sql"><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">expense_group</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
<span class="k">SELECT</span> <span class="p">:</span><span class="n">shared_expense_name</span> <span class="k">WHERE</span> <span class="p">:</span><span class="n">shared_expense_name</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span></code></pre>
<p>Ici, on utilise une requête de type <a href="https://www.sqlite.org/lang_insert.html"><code>INSERT INTO ... SELECT</code></a> pour insérer une nouvelle ligne dans la table <code>expense_group</code>. On ajoute une clause <code>WHERE</code> pour qu'une ligne ne soit insérée que lorsque l'utilisateur a rempli une valeur dans le formulaire, et pas à chaque fois que la page se charge.</p>
<p>La variable SQL <code>:shared_expense_name</code> sera associée à la valeur que l'utilisateur aura rentré dans le champ de texte que nous avons appelé <code>shared_expense_name</code> à l'étape précédente.</p>
<p>Maintenant, chaque validation de formulaire crée une nouvelle ligne dans notre base de données. Il est temps de créer notre premier composant dynamique, dont le contenu va dépendre de ce qu'il y a dans notre base de données. Toujours à la suite, dans <code>index.sql</code>:</p>
<pre><code class="sql"><span class="k">SELECT</span> <span class="s1">'list'</span> <span class="k">as</span> <span class="n">component</span><span class="p">;</span>
<span class="k">SELECT</span>
<span class="n">name</span> <span class="k">AS</span> <span class="n">title</span><span class="p">,</span>
<span class="s1">'group.sql?id='</span> <span class="o">||</span> <span class="n">id</span> <span class="k">AS</span> <span class="n">link</span>
<span class="k">FROM</span> <span class="n">expense_group</span><span class="p">;</span></code></pre>
<p>Ici, nous utilisons un nouvel élément issu de la <a href="https://sql.ophir.dev/documentation.sql">bibliothèque standard de SQLPage</a>: le composant <a href="https://sql.ophir.dev/documentation.sql?component=list#component"><code>list</code></a>. Après l'avoir sélectionné, nous le peuplons avec des données qui viennent de la table <code>expense_group</code> de notre base de données. Pour chaque élément de la liste, nous spécifions un lien vers lequel l'utilisateur sera emmené lorsqu'il cliquera dessus. Pour créer ce lien, nous <a href="https://www.geeksforgeeks.org/sql-concatenation-operator/">concaténons</a> le nom d'un nouveau fichier SQL que nous allons créer, avec une variable qui contient l'identifiant du groupe à afficher.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6c6f7661736f612f53514c706167652f6173736574732f3535323632392f38653331626633632d663132362d343738392d393231652d376134303364643164333166/8e31bf3c-f126-4789-921e-7a403dd1d31f" alt="Liste dynamique avec SQLPage" title="Source : https://github.com/lovasoa/SQLpage/assets/552629/8e31bf3c-f126-4789-921e-7a403dd1d31f"></p>
<h3 id="toc-amélioration-de-lapplication-création-de-nouvelles-pages">Amélioration de l'application, création de nouvelles pages</h3>
<p>Nous avons maintenant vu tous les éléments nécessaires à la construction d'une application. Il ne nous reste plus qu'à les appliquer à la création des pages restantes de notre application opensource.</p>
<p>Dans <code>group.sql</code>, réutilisons les composants from et list que nous connaissons maintenant :</p>
<pre><code class="sql"><span class="k">SELECT</span> <span class="s1">'title'</span> <span class="k">as</span> <span class="n">component</span><span class="p">,</span> <span class="n">name</span> <span class="k">as</span> <span class="n">contents</span>
<span class="k">FROM</span> <span class="n">expense_group</span> <span class="k">WHERE</span> <span class="n">id</span> <span class="o">=</span> <span class="err">$</span><span class="n">id</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">group_member</span><span class="p">(</span><span class="n">group_id</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
<span class="k">SELECT</span> <span class="err">$</span><span class="n">id</span><span class="p">,</span> <span class="p">:</span><span class="n">new_member_name</span> <span class="k">WHERE</span> <span class="p">:</span><span class="n">new_member_name</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="s1">'list'</span> <span class="k">as</span> <span class="n">component</span><span class="p">,</span> <span class="s1">'Membres'</span> <span class="k">as</span> <span class="n">title</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="n">name</span> <span class="k">AS</span> <span class="n">title</span> <span class="k">FROM</span> <span class="n">group_member</span> <span class="k">WHERE</span> <span class="n">group_id</span><span class="o">=</span><span class="err">$</span><span class="n">id</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="s1">'form'</span> <span class="k">as</span> <span class="n">component</span><span class="p">,</span> <span class="s1">'Ajouter un membre au groupe'</span> <span class="k">as</span> <span class="n">validate</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="s1">'Nom du membre'</span> <span class="k">AS</span> <span class="s1">'label'</span><span class="p">,</span> <span class="s1">'new_member_name'</span> <span class="k">AS</span> <span class="n">name</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="s1">'title'</span> <span class="k">as</span> <span class="n">component</span><span class="p">,</span> <span class="s1">'Dépenses'</span> <span class="k">as</span> <span class="n">contents</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="s1">'form'</span> <span class="k">as</span> <span class="n">component</span><span class="p">,</span> <span class="s1">'Ajouter une dépense'</span> <span class="k">as</span> <span class="n">title</span><span class="p">,</span> <span class="s1">'Ajouter'</span> <span class="k">as</span> <span class="n">validate</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="s1">'Description'</span> <span class="k">AS</span> <span class="n">name</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="s1">'Montant'</span> <span class="k">AS</span> <span class="n">name</span><span class="p">,</span> <span class="s1">'number'</span> <span class="k">AS</span> <span class="k">type</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="s1">'select'</span> <span class="k">as</span> <span class="k">type</span><span class="p">,</span> <span class="s1">'Dépensé par'</span> <span class="k">AS</span> <span class="n">name</span><span class="p">,</span>
<span class="n">json_group_array</span><span class="p">(</span><span class="n">json_object</span><span class="p">(</span><span class="ss">"label"</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="ss">"value"</span><span class="p">,</span> <span class="n">id</span><span class="p">))</span> <span class="k">as</span> <span class="k">options</span>
<span class="k">FROM</span> <span class="n">group_member</span> <span class="k">WHERE</span> <span class="n">group_id</span> <span class="o">=</span> <span class="err">$</span><span class="n">id</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">expense</span><span class="p">(</span><span class="n">spent_by</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">amount</span><span class="p">)</span>
<span class="k">SELECT</span> <span class="p">:</span><span class="ss">"Dépensé par"</span><span class="p">,</span> <span class="p">:</span><span class="n">Description</span><span class="p">,</span> <span class="p">:</span><span class="n">Montant</span> <span class="k">WHERE</span> <span class="p">:</span><span class="n">Montant</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="s1">'card'</span> <span class="k">as</span> <span class="n">component</span><span class="p">,</span> <span class="s1">'Dépenses'</span> <span class="k">as</span> <span class="n">title</span><span class="p">;</span>
<span class="k">SELECT</span>
<span class="n">expense</span><span class="p">.</span><span class="n">name</span> <span class="k">as</span> <span class="n">title</span><span class="p">,</span>
<span class="s1">'Par '</span> <span class="o">||</span> <span class="n">group_member</span><span class="p">.</span><span class="n">name</span> <span class="o">||</span> <span class="s1">', le '</span> <span class="o">||</span> <span class="n">expense</span><span class="p">.</span><span class="nb">date</span> <span class="k">as</span> <span class="n">description</span><span class="p">,</span>
<span class="n">expense</span><span class="p">.</span><span class="n">amount</span> <span class="o">||</span> <span class="s1">' €'</span> <span class="k">as</span> <span class="n">footer</span><span class="p">,</span>
<span class="k">CASE</span> <span class="k">WHEN</span> <span class="n">expense</span><span class="p">.</span><span class="n">amount</span> <span class="o">></span> <span class="mi">100</span> <span class="k">THEN</span> <span class="s1">'red'</span> <span class="k">WHEN</span> <span class="n">expense</span><span class="p">.</span><span class="n">amount</span> <span class="o">></span> <span class="mi">50</span> <span class="k">THEN</span> <span class="s1">'orange'</span> <span class="k">ELSE</span> <span class="s1">'blue'</span> <span class="k">END</span> <span class="k">AS</span> <span class="n">color</span>
<span class="k">FROM</span> <span class="n">expense</span>
<span class="k">INNER</span> <span class="k">JOIN</span> <span class="n">group_member</span> <span class="k">on</span> <span class="n">expense</span><span class="p">.</span><span class="n">spent_by</span> <span class="o">=</span> <span class="n">group_member</span><span class="p">.</span><span class="n">id</span>
<span class="k">WHERE</span> <span class="n">group_member</span><span class="p">.</span><span class="n">group_id</span> <span class="o">=</span> <span class="err">$</span><span class="n">id</span><span class="p">;</span></code></pre>
<p>Nous avons ici créé une seule page, qui contient plusieurs listes et plusieurs formulaires, juste en écrivant nos requêtes SQL les unes après les autres dans notre fichier. </p>
<p>Le seul point particulier à noter, qui est différent de ce que nous avons vu avant, est l'utilisation de la fonction sql <a href="https://www.sqlite.org/json1.html#jgrouparray"><code>json_group_array</code></a> pour remplir la valeur du champ de formulaire à choix multiple, qui prend un tableau json comme valeur.</p>
<p>Nous arrivons au résultat suivant :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6769746875622e636f6d2f6c6f7661736f612f53514c706167652f6173736574732f3535323632392f31316338366331652d336261642d343364312d393162622d326538396465313731613037/11c86c1e-3bad-43d1-91bb-2e89de171a07" alt="screenshot sqlpage" title="Source : https://github.com/lovasoa/SQLpage/assets/552629/11c86c1e-3bad-43d1-91bb-2e89de171a07"></p>
<h4 id="toc-cerise-sur-le-gâteau--calcul-des-dettes">Cerise sur le gâteau : calcul des dettes</h4>
<p>Une fonctionnalité pratique de l'application originelle est le calcul du tableau de dette. L'application fait elle-même le calcul final de qui doit combien. C'est un peu moins trivial que les requêtes classiques de listage de données que l'on a vues jusqu'ici, mais on peut aussi implémenter cela entièrement en SQL.</p>
<p>On crée quelques <a href="https://fr.wikipedia.org/wiki/Vue_(base_de_donn%C3%A9es)">vues</a> qui nous seront utiles pour nos calculs. </p>
<p>Dans SQLPage, on ne crée en général pas de fonctions, et on n'importe pas des bibliothèques. Pour construire une fonctionnalité complexe, le plus simple est de construire des vues successives de nos données, dans lesquelles on les groupe et les filtre comme on le suite. Ici, on construit les trois vues simples suivantes, chacune avec sa fonction SQL :</p>
<ul>
<li>
<a href="https://github.com/lovasoa/SQLpage/blob/main/examples/splitwise/sqlpage/migrations/0001_views.sql#L3-L9"><code>members_with_expenses</code></a>, qui va lier nos tables entre elles pour associer les noms des membres à leurs montants de dépenses.</li>
<li>
<a href="https://github.com/lovasoa/SQLpage/blob/main/examples/splitwise/sqlpage/migrations/0001_views.sql#L12-L16"><code>average_debt_per_person</code></a> qui va diviser le montant total dépensé par le groupe par le nombre de participants.</li>
<li>
<a href="https://github.com/lovasoa/SQLpage/blob/main/examples/splitwise/sqlpage/migrations/0001_views.sql#L19-L27"><code>individual_debts</code></a> qui va soustraire la dépense moyenne aux dépenses personnelles de chacun, pour savoir combien il doit ou combien on lui doit. </li>
</ul>
<p>Ici c'est du SQL classique, il n'y a rien qui soit propre à SQLPage. Je vous laisse lire <a href="https://github.com/lovasoa/SQLpage/blob/main/examples/splitwise/sqlpage/migrations/0001_views.sql">les 27 lignes de code sur github</a>. </p>
<h2 id="toc-conclusion">Conclusion</h2>
<p>Nous avons vu comment construire une application web complète entièrement en SQL grâce à SQLPage. Nous pouvons maintenant la faire tourner sur un tout petit serveur chez nous, dans le cloud, ou même <a href="https://github.com/lovasoa/SQLpage#serverless">en mode sans-serveur</a>. SQLPage est écrit dans le langage de programmation <em>rust</em> et consomme très peu de resources par rapport à une application web classique, l'application sera donc très peu chère à héberger.</p>
<h4 id="toc-pour-résumer-ce-que-nous-avons-vu">Pour résumer ce que nous avons vu</h4>
<p>Nous avons tout d'abord <a href="#toc-premi%C3%A8re-%C3%A9tape-choisir-un-sch%C3%A9ma-pour-notre-base-de-donn%C3%A9es">créé une structure de base de données grâce aux migrations</a>.</p>
<p>Ensuite, nous avons affiché des composants graphiques grâce à la <a href="https://sql.ophir.dev/documentation.sql">bibliothèque de composants intégrés</a> de SQLPage.</p>
<p>Enfin, nous avons inséré des données dynamiquement dans notre base de données grâce au système de variables de SQLPage.</p>
<h4 id="toc-pour-aller-plus-loin">Pour aller plus loin</h4>
<p>SQLPage est un logiciel libre et gratuit. Si vous rencontrez des problèmes lors de son utilisation, n'hésitez pas à <a href="https://github.com/lovasoa/SQLpage/issues">rapporter un bug ou demander une fonctionnalité sur github</a>, ou à discuter de son utilisation sur les <a href="https://github.com/lovasoa/SQLpage/discussions">pages de discussion</a>.</p>
<p>Et si vous cherchez une idée pour vous entraîner… Pourquoi pas un <a href="//linuxfr.org/wiki/taptempo">TapTempo</a> entièrement en SQL ?</p>
</div><div><a href="https://linuxfr.org/news/ecrire-une-appli-web-en-une-journee-avec-sqlpage.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/131690/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/ecrire-une-appli-web-en-une-journee-avec-sqlpage#comments">ouvrir dans le navigateur</a>
</p>
lovasoaNÿcoBenoît SibaudgUIBAudbobble bubblehttps://linuxfr.org/nodes/131690/comments.atomtag:linuxfr.org,2005:News/414972023-05-24T09:07:58+02:002023-05-24T09:07:58+02:00LoTemplate générateur de documents à partir d'ODTLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>LoTemplate est une brique libre (api, cli, lib) sous licence AGPLv3 et destinée aux développeurs et aux développeuses cherchant à intégrer dans leur solution un générateur de documents (rapport, lettre,…). Les solutions existantes pour faire cela sont variées (<a href="https://wkhtmltopdf.org/">wkhtmltopdf</a>, <a href="https://fr.m.wikipedia.org/wiki/JasperReports">JasperReports</a>, <a href="https://fr.m.wikipedia.org/wiki/Business_Intelligence_and_Reporting_Tools">BIRT</a>…) mais toutes demandent systématiquement de créer des modèles de documents en HTML, XML ou autre.</p>
<p>De notre côté, nous avions un besoin précis avec une contrainte : pouvoir générer des documents DOC, PDF et ODT à partir de modèles éditables par la famille Michu (Monsieur tout le monde). Ne trouvant rien en libre, nous nous sommes retroussés les manches. Nous avons donc développé LoTemplate pour permettre de générer des PDF, DOC, DOCX ou ODT depuis des documents LibreOffice servant de modèles. L’objectif est de pouvoir intégrer LoTemplate rapidement dans un projet, c’est pourquoi, il peut être utilisé via une API, en module Python ou un CLI. Les briques techniques utilisées sont LibreOffice (en mode headless), Python et <a href="https://fr.wikipedia.org/wiki/Flask_(framework)">Flask</a> pour l’API.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f7374617469717565732e70726f62657379732e636f6d2f6c6f676f2d6c6f74656d706c6174652d636f756c657572732e706e67/logo-lotemplate-couleurs.png" alt="Logo du projet" title="Source : https://statiques.probesys.com/logo-lotemplate-couleurs.png"></p>
</div><ul><li>lien nᵒ 1 : <a title="https://github.com/Probesys/lotemplate" hreflang="fr" href="https://linuxfr.org/redirect/112109">Github</a></li></ul><div><p>LoTemplate va permettre de faire cela à partir d’un document LibreOffice utilisant la nomenclature LoTemplate pour les variables. </p>
<p>Ainsi, chaque solution intégrant LoTemplate pourra permettre à l’utilisateur lambda de partir de ses documents Office pour intégrer ses modèles dans l’application sans avoir à maîtriser des technologies spécifiques et complexes. C’est pour cela que LoTemplate offre une vraie innovation pour la gestion de modèles de documents dans les applications Web.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f7374617469717565732e70726f62657379732e636f6d2f736368656d615f6c6f74656d706c6174652e706e67/schema_lotemplate.png" alt="Schéma du fonctionnement : des données au format final en passant par le module via Docker" title="Source : https://statiques.probesys.com/schema_lotemplate.png"></p>
<p>Les développeurs et développeuses trouveront :</p>
<ul>
<li>un exemple d’utilisation très parlant dans la <a href="https://github.com/Probesys/lotemplate#quick-start-with-the-api">doc</a> ;</li>
<li>des exemples dans les <a href="https://github.com/Probesys/lotemplate/tree/master/lotemplate/unittest/files/templates">tests unitaires</a> ;</li>
<li>la présentation pdf de <a href="https://statiques.probesys.com/Pres_LOTemplate2023.pdf">LoTemplate</a>.</li>
</ul>
<p>LoTemplate est en production depuis un an chez nous et de nombreuses améliorations sont dans les cartons : gestion des formats <a href="https://fr.wikipedia.org/wiki/OpenDocument">ODS</a>, <a href="https://fr.wikipedia.org/wiki/XLS">XLS</a>, gestion des structures de contrôle, etc.</p>
<p>N’hésitez donc pas à l’utiliser, faire vos retours et, bien sûr, contribuer.</p>
</div><div><a href="https://linuxfr.org/news/lotemplate-generateur-de-documents-a-partir-d-odt.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/131177/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/lotemplate-generateur-de-documents-a-partir-d-odt#comments">ouvrir dans le navigateur</a>
</p>
zozoNÿcopalm123Ysabeau 🧶 🧦bobble bubblepatrick_ghttps://linuxfr.org/nodes/131177/comments.atomtag:linuxfr.org,2005:News/413652023-01-23T18:18:21+01:002023-01-23T18:18:21+01:00Concours ICRA de robot humanoïdes lutteursLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Basé entièrement sur une pile de logiciels libres, dont le simulateur de robots <a href="https://github.com/cyberbotics/webots">Webots</a>, le concours de programmation de robots humanoïdes lutteurs a démarré la semaine dernière sur <a href="https://webots.cloud/run?version=R2023a&url=https://github.com/cyberbotics/wrestling/blob/main/worlds/wrestling.wbt&type=competition">webots.cloud</a>. On peut déjà y voir les premiers matches en 3D où deux robots NAO s’affrontent sur un ring de catch.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f6379626572626f746963732f77726573746c696e672f6d61696e2f707265766965772f7468756d626e61696c2e6a7067/thumbnail.jpg" alt="Concours de robots humanoïdes lutteurs" title="Source : https://raw.githubusercontent.com/cyberbotics/wrestling/main/preview/thumbnail.jpg"></p>
<p>La finale aura lieu le 2 juin 2023 lors de la conférence ICRA 2023 à Londres, mais il est possible de participer à distance. Le gagnant recevra <a href="https://www.google.com/search?q=ethereum+price">un Ethereum</a>. La participation est ouverte à tous jusqu’au 23 mai, mais il est recommandé de <a href="https://github.com/cyberbotics/wrestling/">s’inscrire</a> le plus tôt possible.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://webots.cloud/run?version=R2023a&url=https://github.com/cyberbotics/wrestling/blob/main/worlds/wrestling.wbt&type=competition" hreflang="en" href="https://linuxfr.org/redirect/111637">webots.cloud</a></li><li>lien nᵒ 2 : <a title="https://github.com/cyberbotics/wrestling/" hreflang="en" href="https://linuxfr.org/redirect/111638">Page GitHub du concours</a></li></ul><div><p>Ce concours de programmation vise à promouvoir le développement de robots intelligents.</p>
<p>Les participants doivent programmer le comportement d’un robot qui combat un autre robot sur un ring de catch. Tous les coups sont permis !</p>
<p>La programmation peut se faire dans n’importe quel langage de programmation, des exemples sont fournis en Python, mais on peut tout aussi bien utiliser C, C++, Java, etc. et même <a href="https://www.ros.org">ROS</a>.</p>
<p>Chaque robot virtuel possède un certain nombre de capteurs, dont deux caméras, un accéléromètre, un gyroscope, des capteurs tactiles, etc.</p>
<p>Le code des participants est hébergé sur GitHub (en privé ou en public selon que le participant souhaite ou non partager son code). Chaque fois qu’un participant pousse du code (<code>git push</code>) sur sa branche principale GitHub (<code>main</code>), une série de matches est démarrée automatiquement dans GitHub Actions (le système d’intégration continue de GitHub) et les participants peuvent <a href="https://github.com/cyberbotics/wrestling/actions">examiner les logs</a> et <a href="https://webots.cloud/run?version=R2023a&url=https://github.com/cyberbotics/wrestling/blob/main/worlds/wrestling.wbt&type=competition">voir les matchs</a>.</p>
<p>Les nouveaux participants entrent par le bas du tableau (leaderboard) et grimpent dans le classement tant qu’ils gagnent leurs matchs, jusqu’à arriver au sommet…</p>
<p>Toute l’infrastructure du concours est open-source. Elle est hébergée sur GitHub. Il est même possible de créer facilement son propre concours de programmation de robots avec son propre scénario en <a href="https://github.com/cyberbotics/competition-template/">réutilisant cette infrastructure</a>.</p>
</div><div><a href="https://linuxfr.org/news/concours-icra-de-robot-humanoides-lutteurs.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/130087/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/concours-icra-de-robot-humanoides-lutteurs#comments">ouvrir dans le navigateur</a>
</p>
oliviermichel0Benoît Sibaudpalm123https://linuxfr.org/nodes/130087/comments.atomtag:linuxfr.org,2005:News/412322022-12-23T20:49:23+01:002022-12-23T20:49:23+01:00Tomtom, sdcard et système embarqué : accéder au système de fichiersLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Les systèmes embarqués sur véhicules peuvent s'avérer problématiques. Ici il s'agira d'un témoignage de <a href="//linuxfr.org/redirect/111193">Sébastien dans son journal</a> sur Renault, à l’origine de cette dépêche. </p>
</div><ul><li>lien nᵒ 1 : <a title="https://fr.wikipedia.org/wiki/Renault_R-Link" hreflang="fr" href="https://linuxfr.org/redirect/111288">Page Wikipedia sur Renault R-Link</a></li><li>lien nᵒ 2 : <a title="http://elm-chan.org/docs/fat_e.html" hreflang="en" href="https://linuxfr.org/redirect/111289">FAT32 documentation</a></li><li>lien nᵒ 3 : <a title="https://en.wikipedia.org/wiki/BIOS_parameter_block" hreflang="en" href="https://linuxfr.org/redirect/111290">Page Wikipedia du BIOS parameter block</a></li><li>lien nᵒ 4 : <a title="https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf" hreflang="en" href="https://linuxfr.org/redirect/111291">Microsoft FAT32 specifications</a></li><li>lien nᵒ 5 : <a title="https://linuxfr.org/users/usawa/journaux/tomtom-sdcard-et-systeme-embarque-acceder-au-systeme-de-fichiers" hreflang="fr" href="https://linuxfr.org/redirect/111292">Précédent journal de l'auteur sur le même sujet</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-r-link-renault-et-moi-une-histoire-damour">R-Link, Renault et moi, une histoire d’amour</a></li>
<li><a href="#toc-%C3%87a-marche-mais-en-vrai-%C3%A7a-marche-pas">Ça marche, mais en vrai ça marche pas</a></li>
<li><a href="#toc-d%C3%A9bogage">Débogage</a></li>
<li><a href="#toc-on-t%C3%A2tonne">On tâtonne</a></li>
<li><a href="#toc-et-%C3%A7a-fonctionne-mais-menfin">Et ça fonctionne, mais, m’enfin ???</a></li>
</ul>
<h2 id="toc-r-link-renault-et-moi-une-histoire-damour">R-Link, Renault et moi, une histoire d’amour</h2>
<p>Lors d’une précédente aventure, je vous avais expliqué comment j’ai pu, grâce à Linux, accéder au contenu de la carte SD contenant la carte de mon GPS embarqué, un Renault R-Link Evolution, basé sur Tomtom. Avec ça, j’ai pu ajouter, durant deux ans, des points d’intérêt, ce que l’appli fournie par Renault ne permet pas. Las de ne pas disposer de la dernière mise à jour de carte, malgré un abonnement chèrement payé, j’ai acheté la dernière version sur une nouvelle carte SD, fournie par Renault.</p>
<p>Je passerai sur son tarif français de 50 euros, alors qu’il est de moins de 35 euros dans d’autres pays européens, tant nous sommes habitués, en tant que français, à être des vaches à lait. Je passerai aussi sur le fait que la carte SD datant du mois de juin propose une carte 10.85 datant de février, mais que le service de mise à jour en ligne ne propose qu’une version 10.75 datant d’août 2021. Très sympa quand on a payé 70 euros l’année de forfait pour les mises à jour et les services d’info trafic, sans recevoir une seule mise à jour, donc.</p>
<h2 id="toc-Ça-marche-mais-en-vrai-ça-marche-pas">Ça marche, mais en vrai ça marche pas</h2>
<p>Tout heureux, la carte fonctionne bien sur le système R-Link, ça navigue. Chouette, je vais pouvoir ajouter mes points d’intérêt, et vérifier s’il y a des mises à jour. Malheur… Comme l’appli de Renault ne fonctionne que sur Windows (et MacOS, mais bon, j’aime le risque), j’insère la carte SD dans ma machine, et Windows me demande si je veux formater le disque ! Mais non ?! L’appli Renault ne détecte pas la carte. Aucune mise à jour possible, aucun ajout de nouveaux composants possible. </p>
<p>Je suis un peu borné : c’est une carte SD officielle, elle fonctionne sur le R-Link (basé sur du Linux), c’est en principe un système de fichiers FAT32 qui contient plusieurs fichiers formant RAID Linear, ça doit fonctionner. Je passe sous Linux, pensant que la partition était passée en ext4. Que nenni ! Linux détecte automatiquement la partition et monte le système de fichiers qui est bien en FAT32. Je retrouve mes petits fichiers. Mais alors, quel est le problème ?</p>
<p>Avant que vous vous demandiez pourquoi je ne prends pas une autre carte SD pour y recopier les fichiers, ce n’est pas possible: le système vérifie le CID de carte SD, qui est liée à la carte tomtom (mais aussi au numéro de série de la voiture, inscrit à la première insertion de la carte). Il faudrait disposer d’une carte SD dont le CID peut être modifié, mais aussi d’un adaptateur spécial, car la manipulation n’est pas possible avec un adaptateur USB. Et si vous vous demandez pourquoi je n’ai pas juste reformaté et remis la carte : je me suis méfié, peut-être que la partition et le système de fichiers nécessitent des paramètres particuliers ? (non, mais je ne le savais pas encore) Il va falloir plonger les mains dans la mécanique.</p>
<h2 id="toc-débogage">Débogage</h2>
<p>Première étape, un petit <code>fdisk -l</code>, qui ne montre rien de spécial, c’est une table MBR, compatible DOS, avec une partition de type c pour Windows 95 FAT32 LBA. Classique.</p>
<pre><code>Disque /dev/sdc : 15,23 GiB, 16357785600 octets, 31948800 secteurs
Disk model: Multiple Reader
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d’E/S (minimale / optimale) : 512 octets / 512 octets
Type d’étiquette de disque : dos
Identifiant de disque : 0xa5eb573a
Périphérique Amorçage Début Fin Secteurs Taille Id Type
/dev/sdc1 * 2048 31948799 31946752 15,2G c W95 FAT32 (LBA)
</code></pre>
<p>Je passe à l’analyse du contenu du block device, qui révèle des surprises: table de partition invalide ? Mauvais offset ? Mais qu’est-ce qu’ils ont pu bien utiliser pour pondre un truc pareil ?</p>
<pre><code>$ sudo file -s /dev/sdc
/dev/sdc: DOS/MBR boot sector MS-MBR Windows 7 english at offset 0x163 "Invalid partition table" at offset 0x17b "Error loading operating system" at offset 0x19a "Missing operating system", disk signature 0xa5eb573a; partition 1 : ID=0xc, active, start-CHS (0x0,32,33), end-CHS (0x3ff,254,63), startsector 2048, 31946752 sectors
</code></pre>
<p>Je fais la même chose sur le block device de la partition sdc1, et je suis assez surpris par les divers paramètres du système de fichiers : OEM-ID, Media descriptor, les diverses configurations des secteurs, et notamment les reserved sectors qui ne semblent pas suivre l’alignement. Là encore, il semblerait qu’un simple mkfs sous Linux, ou bouton droit+formater aurait un peu simplifié l'affaire.</p>
<pre><code>$ sudo file -s /dev/sdc1
/dev/sdc1: DOS/MBR boot sector, code offset 0x58+2, OEM-ID "MSDOS5.0", sectors/cluster 64, reserved sectors 394, Media descriptor 0xfa, sectors/track 63, heads 255, hidden sectors 2048, sectors 31946752 (volumes > 32 MB), FAT (32 bit), sectors/FAT 3899, serial number 0xc9fd98, unlabeled
</code></pre>
<h2 id="toc-on-tâtonne">On tâtonne</h2>
<p>Avant de continuer, j’effectue une sauvegarde complète de la carte avec un dd et je copie aussi les fichiers contenus sur la partition. J’utiliserai losetup ensuite sur le fichier image pour le lier à un block device à des fins d’analyse. J’aurai ainsi la possibilité de revenir en arrière si besoin.</p>
<p>Je détruis et recrée la table de partition. Sous fdisk, ça se fait avec la lettre o, pour recréer une table DOS (MBR, donc). Je crée ensuite une partition normale de même type, je passe ici sur les commandes, rien de spécial. Puis, je retente de créer exactement le même système de fichiers FAT32 selon les paramètres trouvés précédemment. J’en arrive au final, à cette commande :</p>
<pre><code>$ sudo mkfs -t vfat -s 64 -R 394 -g 255/63 -i c9fd98 -a -M 0xfa /dev/sdc1
</code></pre>
<p>Bon, aucun souci de détection niveau kernel, ça monte bien sous Linux, je repasse sous Windows : il ne détecte rien, et propose de formater. Mais quoi ? Alors certes :</p>
<ul>
<li>Le paramètre -M permet de modifier le BPB_Media: les valeurs autorisées sont 0xF0, 0xF8 à 0xFF. La valeur standard est 0xF8 pour les périphériques fixes, et 0xF0 pour les amovibles, mais la référence dit que ce n’est plus utile ou très important, une histoire de compatibilité MSDOS 1.0…</li>
<li>Le paramètre -a permet de ne pas tenir compte de l’alignement des secteurs. Avantage: on gagne de la place. Inconvénient, ça ralentit considérablement les performances des périphériques de type flash. Pas malin.</li>
<li>Le paramètre -i est l’ID du volume, je conserve le même, si jamais le programme de protection vérifie ça.</li>
<li>Le nombre de secteurs par cluster est à 64, ce n’est pas courant, mais je pense à un alignement quelconque.</li>
<li>Le nombre de secteurs réservés, 364, semble trop important.</li>
<li>La géométrie n’est pas cohérente avec la structure de la carte SD, mais ça ne devrait pas gêner le fonctionnement.</li>
<li>Il est impossible avec les outils classiques (mkfs.vfat…) de modifier l’OEM-ID. C’est par défaut le nom du programme qui a créé le système de fichiers. Pourtant, après avoir lu quelques docs, ça a longtemps été un paramètre problématique. Ce ne sera pas le cas ici.</li>
</ul>
<h2 id="toc-et-ça-fonctionne-mais-menfin">Et ça fonctionne, mais, m’enfin ???</h2>
<p>Je change des paramètres, et je finis par trouver que Windows ne comprend pas la valeur du BPB_Media. Au final, je crée le système de fichiers en remettant l’alignement (on vire le -a), et en spécifiant 0xf8. Et ça fonctionne ! </p>
<pre><code>$ sudo mkfs -t vfat -s 64 -R 256 -g 255/63 -i c9fd98 -M 0xf8 /dev/sdc1
</code></pre>
<p>Je recopie mes fichiers, je les vois sous Windows, je retourne à ma voiture, la carte est bien chargée. Ouf ! Je reviens sous Windows, l’appli la détecte (mais pas de mise à jour disponible). Mais alors, quel bilan ? Il est double :</p>
<ol>
<li>Renault fournit à ses clients des cartes SD ne pouvant pas être détectées par Windows à cause d’un souci sur les paramètres lors de la création du système de fichiers FAT32. Je me demande pourquoi ils se cassent les pieds avec des valeurs aussi compliquées et quel outil ils utilisent. Mais c’est une boulette. Mon petit doigt me dit qu'ils ne feront rien, en tout cas pour ceux qui n’ont pas eu de chance si une nouvelle version corrigée est diffusée.</li>
<li>Le pilote FAT32 de Windows ne reconnaît pas ses propres paramètres du standard FAT32 créé par Microsoft. C’est pas joli joli ça. J’ai vérifié dans la doc officielle, et ça aurait dû fonctionner. Sur ce point Linux est carré.</li>
</ol>
<p>Au final, j’aurais pu me contenter de recréer un système de fichiers FAT32 avec les paramètres par défaut, et ça aurait très bien fonctionné.</p>
</div><div><a href="https://linuxfr.org/news/tomtom-sdcard-et-systeme-embarque-acceder-au-systeme-de-fichiers.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/129030/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/tomtom-sdcard-et-systeme-embarque-acceder-au-systeme-de-fichiers#comments">ouvrir dans le navigateur</a>
</p>
Sébastien RohautYves BourguignonBenoît Sibaudpalm123FrancescoJulien Jorgehttps://linuxfr.org/nodes/129030/comments.atomtag:linuxfr.org,2005:News/412732022-11-25T09:30:33+01:002022-12-03T14:05:26+01:00Nouveaux préfixes SI et avenir de la seconde intercalaireLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Le Bureau international des poids et mesures est chargé de coordonner le système international d'unités. Depuis la définition du mètre comme étant <em>la longueur du trajet parcouru dans le vide par la lumière pendant une durée de 1/299 792 458 de seconde</em>, toutes les unités sont définies par des phénomènes physiques reproductibles.</p>
<p>La conférence du BIPM était réunie la semaine dernière, et a pris plusieurs résolutions qui vont affecter les applications technologiques.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://www.bipm.org/documents/20126/64811223/Resolutions-2022.pdf/281f3160-fc56-3e63-dbf7-77b76500990f" hreflang="fr" href="https://linuxfr.org/redirect/111328">Lite des résolutions 2022</a></li><li>lien nᵒ 2 : <a title="https://www.bipm.org/fr/measurement-units/si-prefixes" hreflang="fr" href="https://linuxfr.org/redirect/111329">Préfixes du SI</a></li><li>lien nᵒ 3 : <a title="https://www.bipm.org" hreflang="fr" href="https://linuxfr.org/redirect/111330">Bureau international des poids et mesures</a></li><li>lien nᵒ 4 : <a title="https://www.iers.org/" hreflang="en" href="https://linuxfr.org/redirect/111331">International Earth rotation and reference systems service</a></li></ul><div><h3 id="toc-nouveaux-préfixes-du-si">Nouveaux préfixes du SI</h3>
<p>Les nombres manipulés en sciences et technologies sont de plus en plus importants, aussi pour prendre en compte ces nouveaux besoins de nouveaux préfixes multiplicateurs d'unités ont été définis :</p>
<table>
<thead>
<tr>
<th>Facteur</th>
<th>Préfixe</th>
<th>Symbole</th>
</tr>
</thead>
<tbody>
<tr>
<td>10<sup>27</sup>
</td>
<td>ronna</td>
<td>R</td>
</tr>
<tr>
<td>10<sup>-27</sup>
</td>
<td>ronto</td>
<td>r</td>
</tr>
<tr>
<td>10<sup>30</sup>
</td>
<td>quetta</td>
<td>Q</td>
</tr>
<tr>
<td>10<sup>-30</sup>
</td>
<td>quecto</td>
<td>q</td>
</tr>
</tbody>
</table>
<p>Ces préfixes sont donc ajoutés à :</p>
<table>
<thead>
<tr>
<th>Facteur</th>
<th>Préfixe</th>
<th>Symbole</th>
</tr>
</thead>
<tbody>
<tr>
<td>10<sup>24</sup>
</td>
<td>yotta</td>
<td>Y</td>
</tr>
<tr>
<td>10<sup>21</sup>
</td>
<td>zetta</td>
<td>Z</td>
</tr>
<tr>
<td>10<sup>18</sup>
</td>
<td>exa</td>
<td>E</td>
</tr>
<tr>
<td>10<sup>15</sup>
</td>
<td>péta</td>
<td>P</td>
</tr>
<tr>
<td>10<sup>12</sup>
</td>
<td>téra</td>
<td>T</td>
</tr>
<tr>
<td>10<sup>9</sup>
</td>
<td>giga</td>
<td>G</td>
</tr>
<tr>
<td>10<sup>6</sup>
</td>
<td>méga</td>
<td>M</td>
</tr>
<tr>
<td>10<sup>3</sup>
</td>
<td>kilo</td>
<td>k</td>
</tr>
<tr>
<td>10<sup>2</sup>
</td>
<td>hecto</td>
<td>h</td>
</tr>
<tr>
<td>10<sup>1</sup>
</td>
<td>déca</td>
<td>da</td>
</tr>
<tr>
<td>10<sup>–1</sup>
</td>
<td>déci</td>
<td>d</td>
</tr>
<tr>
<td>10<sup>–2</sup>
</td>
<td>centi</td>
<td>c</td>
</tr>
<tr>
<td>10<sup>–3</sup>
</td>
<td>milli</td>
<td>m</td>
</tr>
<tr>
<td>10<sup>–6</sup>
</td>
<td>micro</td>
<td>µ</td>
</tr>
<tr>
<td>10<sup>–9</sup>
</td>
<td>nano</td>
<td>n</td>
</tr>
<tr>
<td>10<sup>–12</sup>
</td>
<td>pico</td>
<td>p</td>
</tr>
<tr>
<td>10<sup>–15</sup>
</td>
<td>femto</td>
<td>f</td>
</tr>
<tr>
<td>10<sup>–18</sup>
</td>
<td>atto</td>
<td>a</td>
</tr>
<tr>
<td>10<sup>–21</sup>
</td>
<td>zepto</td>
<td>z</td>
</tr>
<tr>
<td>10<sup>–24</sup>
</td>
<td>yocto</td>
<td>y</td>
</tr>
</tbody>
</table>
<h3 id="toc-fin-de-la-seconde-intercalaire">Fin de la seconde intercalaire ?</h3>
<p>Le temps universel coordonné, appelé UTC, est établi par le BIPM en considérant qu'une seconde correspond à la durée de 9 192 631 770 oscillations de la fréquence de transition hyperfine de l'atome de césium. De son côté, le service international de la rotation terrestre et des systèmes de référence établit le temps astronomique, appelé UT1, avec une seconde définie comme 1/86400ème d'une rotation complète (jour). Le temps UT1 est physiquement instable, la rotation de la planète ayant tendance à décéreler depuis la mise en place du temps universel.<br>
Aussi, lorsque UT1 est décalé de plus de 0,9 secondes de UTC, le temps UTC est modifié pour prendre en compte le décalage (au 30 juin ou 31 décembre, la seconde 23:59:60 est ajoutée entre 23:59:59 et 00:00:00). Cette modification est censée être prise en compte par les systèmes, mais cela ne se fait pas toujours sans bug (23:59:60 n'est pas toujours bien géré par les logiciels, tout comme les jours de 86401 secondes). Pire, la rotation est actuellement en train d’accélérer, et rien n'a été prévu pour supprimer une seconde à UTC.<br>
La décision a donc été prise de compter sur une prévision des variations astronomiques des 100 prochaines années pour définir les décalages à venir entre UTC et UT1, puis on fixera alors une heure UTC stable qui minimisera le décalage de celui-ci pendant ce siècle. Cette modification devra être réalisée d'ici 2035. Pour le moment on ignore l'ordre de grandeur de ce décalage et comment celui-ci sera implémenté (si la valeur doit être augmentée on passera a priori par des ajouts de secondes intercalaires quelques années de suite).</p>
</div><div><a href="https://linuxfr.org/news/nouveaux-prefixes-si-et-avenir-de-la-seconde-intercalaire.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/129368/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/nouveaux-prefixes-si-et-avenir-de-la-seconde-intercalaire#comments">ouvrir dans le navigateur</a>
</p>
Denis DordoigneBenoît SibaudXavier TeyssierPierre JarillonYsabeau 🧶 🧦bobble bubblehttps://linuxfr.org/nodes/129368/comments.atomtag:linuxfr.org,2005:News/412422022-11-04T21:36:18+01:002022-11-04T21:36:18+01:00Jubako et Arx, un conteneur universel et son format d’archiveLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><h2 id="toc-jubako-quezako">Jubako, quezako ?</h2>
<p><a href="https://en.wikipedia.org/wiki/J%C5%ABbako">重箱 (Jūbako)</a> est le nom japonais des boîtes à bento. Ce sont des boîtes compartimentées qui peuvent se composer en fonction de ce qu’il y a à stocker dedans (en général un repas).</p>
<p>Et ça tombe bien, parce que Jubako, c’est un format de conteneur qui permet de stocker différentes données et méta-données. J’ai tendance à parler de conteneurs plutôt que d’archives, en effet « archive » est un mot orienté qui fait penser aux archives de fichiers, alors que Jubako se veut généraliste : un conteneur Jubako pourrait être utilisé pour plein d’autres choses : empaquetage d’applications, pack de ressources dans un binaire, conteneur multimédia, etc.</p>
<p>Vous pouvez voir Jubako comme étant au stockage ce que XML est à la sérialisation. XML définit comment sérialiser du contenu (sous forme d’un arbre de nœuds avec des attributs) mais ne définit pas quels sont ces nœuds et attributs. Chaque cas d’usage a sa propre structure. Pour Jubako c’est pareil, il définit comment stocker des données dans un fichier « d’archive » mais il ne définit pas quelles sont ces données. Chaque cas d’usage aura sa propre structure de données.</p>
<p>Jubako et Arx sont sous licence MIT.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://framagit.org/jubako/spec" hreflang="en" href="https://linuxfr.org/redirect/111210">Les spécifications de Jubako</a></li><li>lien nᵒ 2 : <a title="https://framagit.org/jubako/jubako" hreflang="wq" href="https://linuxfr.org/redirect/111211">Les sources de la bibliothèque Jubako</a></li><li>lien nᵒ 3 : <a title="https://framagit.org/jubako/arx" hreflang="wq" href="https://linuxfr.org/redirect/111212">Les sources de l'outil d'archivage Arx</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-un-peu-de-contexte">Un peu de contexte</a></li>
<li><a href="#toc-le-format-jubako">Le format Jubako</a></li>
<li><a href="#toc-la-biblioth%C3%A8que-jubako">La bibliothèque Jubako</a></li>
<li>
<a href="#toc-loutil-arx">L’outil Arx</a><ul>
<li><a href="#toc-tar">Tar</a></li>
<li><a href="#toc-le-format-arx"> Le format Arx</a></li>
<li>
<a href="#toc-un-peu-de-comparaison">Un peu de comparaison</a><ul>
<li><a href="#toc-pour-la-partie-documentation-seulement-9194-entr%C3%A9es">Pour la partie documentation seulement (9194 entrées) :</a></li>
<li><a href="#toc-pour-la-partie-drivers-seulement-33056-entr%C3%A9es">Pour la partie drivers seulement (33056 entrées) :</a></li>
<li><a href="#toc-et-pour-les-sources-compl%C3%A8tes-81958-entr%C3%A9es">Et pour les sources complètes (81958 entrées) :</a></li>
<li><a href="#toc-la-taille">La taille</a></li>
<li><a href="#toc-le-temps-de-cr%C3%A9ation">Le temps de création</a></li>
<li><a href="#toc-le-temps-dextraction">Le temps d’extraction</a></li>
<li><a href="#toc-le-temps-de-listing">Le temps de listing</a></li>
<li><a href="#toc-le-temps-de-dumping">Le temps de dumping</a></li>
<li><a href="#toc-mount">Mount</a></li>
</ul>
</li>
<li><a href="#toc-utilisation-m%C3%A9moire">Utilisation mémoire</a></li>
</ul>
</li>
<li><a href="#toc-conclusion">Conclusion</a></li>
</ul>
<p>Dans cette dépêche on présente Jubako, un méta-format de conteneur et la bibliothèque associée pour lire et écrire ce format, ainsi qu’Arx, un outil se basant sur Jubako pour faire des archives de fichiers, un peu comme tar mais <s>en mieux</s> différemment. La partie Jubako peut être un peu longue, si vous êtes plus intéressé par la finalité du Jubako, vous pouvez directement sauter à <a href="#toc-loutil-arx">la partie Arx</a> pour voir l’usage qui en est fait.</p>
<h2 id="toc-un-peu-de-contexte">Un peu de contexte</h2>
<p>Mon métier, c’est développeur logiciel, en tant qu’indépendant.<br>
Mon client principal depuis six ans, c’est la fondation Kiwix pour qui je maintiens les outils de base de Kiwix, c’est-à-dire les outils bas niveau en C++ pour lire et créer les archives Zim (pour ce qui nous concerne ici). <br>
Les <a href="https://wiki.openzim.org/wiki/ZIM_file_format">archives Zim</a> sont des archives de « contenu » qui pourraient s’apparenter à des zip, mais avec un format interne qui permet une meilleure compression tout en permettant un accès en lecture quasiment en <em>random access.</em><br>
Le format ZIM fonctionne bien, mais il souffre de quelques erreurs de jeunesse. À la base, ZIM c’est « Zeno IMproved », et Zeno… je sais pas trop d’où ça vient. Il y a bien <a href="https://sourceforge.net/p/tntzenoreader">tntzenoreader</a> qui date de 2007. Mais s’il lit les fichiers Zeno, ce n’est pas lui a priori qui crée le format. (Soit dit en passant, c’est probablement la source de libzim vu que je reconnais pas mal de similitudes de code entre les deux projets). De mémoire, Zeno était associé à l’époque où la fondation Wikimedia parlait de mettre Wikipedia sur CD-ROM.</p>
<p>Enfin bon voilà, un peu de dette technique certes, mais surtout un format très orienté pour le cas d’usage de stocker des pages web. Par exemple, chaque entrée a obligatoirement une url, un titre et un mimetype.</p>
<p>Et puis un jour, celui qu’on ne nomme plus arriva avec son confinement généralisé. Durant cette période, j’ai commencé à travailler sur un nouveau format d’archive, inspiré du format ZIM mais avec comme objectif d’en faire un format généraliste qui pourrait être utilisé par d’autres projets.</p>
<p>Ainsi est né doucement le projet Jubako.</p>
<h2 id="toc-le-format-jubako">Le format Jubako</h2>
<p>Le format Jubako est donc un format (binaire) de fichiers pour des conteneurs. </p>
<p>Jubako est un meta-format. Comme XML, il ne définit pas la structure de données à stocker. Ce sera donc au « Vendeur » (qui utilise Jubako dans son projet) de définir la façon dont les données seront stockées. Il est conçu avec le cas d’usage suivant : un conteneur « read-only » créé une fois et distribué à un ensemble d’utilisateurs qui vont lire le conteneur. La lecture du conteneur doit pouvoir se faire sur des machines de relativement faible puissance et en temps « quasi » constant sans décompression préalable. À l’inverse, il est attendu que la machine sur laquelle est fait le conteneur soit un peu plus puissante (ou tout du moins, qu’il soit accepté que la création prenne plus de temps).</p>
<p>Un conteneur Jubako est composé de plusieurs sous-parties appelées pack :</p>
<ul>
<li>Les <code>contentPack</code>s, qui contiennent les données proprement dites. Les <code>contentPack</code>s sont les plus gros en taille et contiennent les données « brutes », sans métadonnées. C’est un peu un conteneur de blob si on reprend la terminologie de git.</li>
<li>Le <code>directoryPack</code> qui lui contient les entrées et métadonnées associées. C’est le pack le plus complexe et le plus « configurable ». Globalement, il stocke des entrées (<code>Entry</code>). Chaque entrée étant composée de métadonnées et pouvant pointer vers un, plusieurs ou aucun contenu (stocké dans les <code>contentPack</code>s).</li>
<li>Le <code>manifestPack</code> qui relie tous ces différents packs ensemble pour former un tout cohérent.</li>
</ul>
<p>Un conteneur Jubako est donc obligatoirement composé d’un <code>manifestPack</code> et d’un <code>directoryPack</code> et d’un ou plusieurs <code>contentPack</code>s (parfois aucun). Les packs peuvent être stockés séparément (sous forme de fichiers dans un dossier par exemple) ou concaténés ensemble pour ne former qu’un seul fichier.</p>
<p>Notez que si un conteneur est logiquement composé de plusieurs contentPack, il est normal que certains contentPack puissent être manquants lors de la lecture. Cela permet d’avoir un système d’extension de conteneur.<br>
Par exemple, pour un packaging d’application, l’application elle-même pourrait être stockée dans un contentPack toujours présent mais les traductions dans différentes langues serait stockées dans des packs distincts. Le conteneur Jubako référence toutes les entrées (et donc toutes les traductions) mais un utilisateur peut avoir en local un conteneur qui contient seulement le contentPack « application » et la traduction dans sa langue.<br>
Ou encore, le projet kiwix pourrait créer des archives avec les images dans des packs séparés, l’utilisateur téléchargerait des archives « sans » images. Et lorsqu’il a une bonne connexion, télécharge les packs manquants.</p>
<p>Bien sûr, c’est au vendeur de faire en sorte que son application sache quoi faire si un pack est manquant (planter, afficher un joli message d’erreur, proposer à l’utilisateur de télécharger le pack…)</p>
<p>Pour ce qui est des entrées stockées dans le directoryPack, c’est là aussi au vendeur de les définir. Le format des entrées est fixe pour chaque cas d’usage (ou alors il vous faudra gérer de la compatibilité entre les versions).<br>
Pour permettre à Jubako (le lecteur, pas le format) de lire le conteneur, le format des entrées est stocké dans le conteneur lui-même.<br>
Le schéma de données suit le principe suivant :</p>
<ul>
<li>Plusieurs types d’entrées peuvent être stockés dans un seul conteneur. Elles sont stockées (et peuvent être récupérées) séparément. Cela permet par exemple de séparer les données selon leur signification dans le conteneur. Par exemple, les fichiers stockés dans une archive de fichier versus les métadonnées de cette même archive (auteur, date de création…). </li>
<li>Un type d’entrée est composé d’un ou plusieurs variants. C’est l’équivalent d’une union pour celles et ceux qui font du c/c++. Par exemple, le type des entrées dans une archive de fichier sera composée de trois variants <code>Fichier</code>, <code>Dossier</code>, <code>Lien</code>.</li>
<li>Chaque variant est composé d’un ensemble d’attributs (l’équivalent d’un struct en c/c++). Chaque attribut a un type défini (entier signé, non signé, tableau de données (de taille fixe ou non), identifiant de contenu), ce qui permet à Jubako de savoir comment lire ces attributs.
Vous noterez que l’identifiant de contenu n’est qu’un attribut parmi d’autres. Une entrée peut donc pointer sur plusieurs contenus, ou aucun.</li>
</ul>
<p>Chaque variant a sa propre structure, mais il est tout de même conseillé d’avoir des attributs communs entre les variants pour pouvoir faire de la recherche d’entrées basée sur ces attributs.</p>
<p>Toutes les entrées d’un même type sont stockées ensemble dans un sous conteneur (tableau) appelé <code>EntryStore</code>. Comme toutes les entrées ont la même taille, les attributs de taille variable sont déportés dans un <code>ValueStore</code>.<br>
Enfin, des index permettent de pointer sur un sous-ensemble d’entrées dans un <code>EntryStore</code> en particulier. Ces index sont nommés et sont les points d’entrées pour la lecture des conteneurs.<br>
Une application va identifier l’index dont elle a besoin et va « suivre » le fil de la structure de donnée pour lire les entrées et accéder au contenu.</p>
<p>Il y a encore pas mal de choses spécifiées (plus ou moins) telles que des redirections ou des entrées qui étendent (rajoutent des attributs) à d’autres entrées. Mais d’une part, cette présentation est déjà bien trop longue et d’autre part, c’est pas encore implémenté, donc on verra plus tard si vous le voulez bien.</p>
<h2 id="toc-la-bibliothèque-jubako">La bibliothèque Jubako</h2>
<p>Il s’agit de l’implémentation de référence (et unique, pour le moment) pour le format Jubako.<br>
Elle est écrite en Rust parce que <s>c’est à la mode</s> c’est un des rares langages (à ma connaissance) qui soit en même temps de bas niveau (comme le C) et qui fournisse aussi des structures de haut niveau dans sa bibliothèque standard (comme le Python). Qui plus est, il fournit un certain nombre de garanties dans sa gestion mémoire qui permet d’avoir une certaine confiance sur un sujet aussi complexe/sensible que de générer/lire des fichiers binaires. (Ça n’empêche pas les bugs, je vous le garantis aussi :) )</p>
<p>(Je me devais de vous dire dans quel langage était écrit Jubako, mais ce n’est pas le plus important. Oui je sais qu’il y a plein de langages de qualité qui auraient pu être utilisés.)</p>
<p>Je ne vais pas trop m’étendre sur cette partie, l’API de la bibliothèque est pas encore sèche et la doc est inexistante pour le moment. Mais voici quand même un petit aperçu de la création et lecture d’un conteneur Jubako :</p>
<p>(Vous noterez une petite incohérence entre le nom des fonctions et des variables/définitions du format. C’est qu’en écrivant cette dépêche, je me suis rendu compte que la terminologie n’était pas bonne. J’ai écrit la dépêche avec une terminologie modifiée mais le code est encore avec « l’ancienne ».)</p>
<pre><code class="rust"><span class="k">use</span><span class="w"> </span><span class="n">jubako</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">jbk</span><span class="p">;</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">jbk</span>::<span class="n">reader</span>::<span class="n">EntryTrait</span><span class="p">;</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">error</span>::<span class="n">Error</span><span class="p">;</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">rc</span>::<span class="n">Rc</span><span class="p">;</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">typenum</span>::<span class="p">{</span><span class="n">U31</span><span class="p">,</span><span class="w"> </span><span class="n">U40</span><span class="p">,</span><span class="w"> </span><span class="n">U63</span><span class="p">};</span><span class="w"></span>
<span class="c1">// Cela vous permettra de différencier votre conteneur parmi la multitude de conteneurs jubako qui seront créés (oui, oui, j’y crois)</span>
<span class="k">const</span><span class="w"> </span><span class="n">VENDOR_ID</span>: <span class="kt">u32</span> <span class="o">=</span><span class="w"> </span><span class="mh">0x01_02_03_04</span><span class="p">;</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span>-> <span class="nb">Result</span><span class="o"><</span><span class="p">(),</span><span class="w"> </span><span class="nb">Box</span><span class="o"><</span><span class="n">dyn</span><span class="w"> </span><span class="n">Error</span><span class="o">>></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">content_pack</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">ContentPackCreator</span>::<span class="n">new</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="s">"test.jbkc"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">Id</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span><span class="w"> </span><span class="c1">// L’id du pack tel que référencé dans le reste du conteneur</span>
<span class="w"> </span><span class="n">VENDOR_ID</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">FreeData</span>::<span class="o"><</span><span class="n">U40</span><span class="o">></span>::<span class="n">clone_from_slice</span><span class="p">(</span><span class="o">&</span><span class="p">[</span><span class="mh">0x00</span><span class="p">;</span><span class="w"> </span><span class="mi">40</span><span class="p">]),</span><span class="w"> </span><span class="c1">// Mettez ce que vous voulez, c’est pour vous</span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">CompressionType</span>::<span class="n">Zstd</span><span class="p">,</span><span class="w"> </span><span class="c1">// L’algo de compression à utiliser</span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">content_pack</span><span class="p">.</span><span class="n">start</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">directory_pack</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">DirectoryPackCreator</span>::<span class="n">new</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="s">"test.jbkd"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">Id</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="n">VENDOR_ID</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">FreeData</span>::<span class="o"><</span><span class="n">U31</span><span class="o">></span>::<span class="n">clone_from_slice</span><span class="p">(</span><span class="o">&</span><span class="p">[</span><span class="mh">0x00</span><span class="p">;</span><span class="w"> </span><span class="mi">31</span><span class="p">]),</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Les entrées ont une taille fixe. Donc pour les valeurs de taille variable (chaînes de caractères), il nous faut un stockage particulier</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">value_store</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">directory_pack</span><span class="p">.</span><span class="n">create_key_store</span><span class="p">(</span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">KeyStoreKind</span>::<span class="n">Plain</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// On definit une entrée composée de deux variants</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">entry_def</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Entry</span>::<span class="n">new</span><span class="p">(</span><span class="n">vec</span><span class="o">!</span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Variant</span>::<span class="n">new</span><span class="p">(</span><span class="n">vec</span><span class="o">!</span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Key</span>::<span class="n">PString</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="n">Rc</span>::<span class="n">clone</span><span class="p">(</span><span class="o">&</span><span class="n">value_store</span><span class="p">)),</span><span class="w"> </span><span class="c1">// Une chaîne de caractères, à stocker dans value_store</span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Key</span>::<span class="n">new_int</span><span class="p">(),</span><span class="w"> </span><span class="c1">// Un entier</span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Key</span>::<span class="n">ContentAddress</span><span class="p">,</span><span class="w"> </span><span class="c1">// Un "pointeur" sur du contenu.</span>
<span class="w"> </span><span class="p">]),</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Variant</span>::<span class="n">new</span><span class="p">(</span><span class="n">vec</span><span class="o">!</span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Key</span>::<span class="n">PString</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="n">Rc</span>::<span class="n">clone</span><span class="p">(</span><span class="o">&</span><span class="n">value_store</span><span class="p">)),</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Key</span>::<span class="n">new_int</span><span class="p">(),</span><span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Key</span>::<span class="n">new_int</span><span class="p">(),</span><span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="p">]),</span><span class="w"></span>
<span class="w"> </span><span class="p">]);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Le store qui contiendra nos entrées.</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">entry_store_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">directory_pack</span><span class="p">.</span><span class="n">create_entry_store</span><span class="p">(</span><span class="n">entry_def</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">entry_store</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">directory_pack</span><span class="p">.</span><span class="n">get_entry_store</span><span class="p">(</span><span class="n">entry_store_id</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// On ajoute le contenu de notre entrée :</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">content</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">u8</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"Du super contenu de qualité pour notre conteneur de test"</span><span class="p">.</span><span class="n">into</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">reader</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">BufStream</span>::<span class="n">new</span><span class="p">(</span><span class="n">content</span><span class="p">,</span><span class="w"> </span><span class="n">jbk</span>::<span class="n">End</span>::<span class="nb">None</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">content_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">content_pack</span><span class="p">.</span><span class="n">add_content</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="n">reader</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">entry_store</span><span class="p">.</span><span class="n">add_entry</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="c1">// On utilise le variant 0</span>
<span class="w"> </span><span class="n">vec</span><span class="o">!</span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Value</span>::<span class="n">Array</span><span class="p">(</span><span class="s">"Super"</span><span class="p">.</span><span class="n">into</span><span class="p">()),</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Value</span>::<span class="n">Unsigned</span><span class="p">(</span><span class="mi">50</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Value</span>::<span class="n">Content</span><span class="p">(</span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Content</span>::<span class="n">from</span><span class="p">((</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">Id</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span><span class="w"> </span><span class="c1">// L'id de notre pack</span>
<span class="w"> </span><span class="n">content_id</span><span class="p">,</span><span class="w"> </span><span class="c1">// L'id du contenu dans le pack</span>
<span class="w"> </span><span class="p">))),</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">entry_store</span><span class="p">.</span><span class="n">add_entry</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="c1">// On utilise le variant 1</span>
<span class="w"> </span><span class="n">vec</span><span class="o">!</span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Value</span>::<span class="n">Array</span><span class="p">(</span><span class="s">"Mega"</span><span class="p">.</span><span class="n">into</span><span class="p">()),</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Value</span>::<span class="n">Unsigned</span><span class="p">(</span><span class="mi">42</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Value</span>::<span class="n">Unsigned</span><span class="p">(</span><span class="mi">5</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">entry_store</span><span class="p">.</span><span class="n">add_entry</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="c1">// On utilise le variant 1</span>
<span class="w"> </span><span class="n">vec</span><span class="o">!</span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Value</span>::<span class="n">Array</span><span class="p">(</span><span class="s">"Hyper"</span><span class="p">.</span><span class="n">into</span><span class="p">()),</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Value</span>::<span class="n">Unsigned</span><span class="p">(</span><span class="mi">45</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">Value</span>::<span class="n">Unsigned</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// On créé un index qui nous permettra de retrouver nos entrées.</span>
<span class="w"> </span><span class="n">directory_pack</span><span class="p">.</span><span class="n">create_index</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="s">"mon petit index a moi"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">jubako</span>::<span class="n">ContentAddress</span>::<span class="n">new</span><span class="p">(</span><span class="mf">0.</span><span class="n">into</span><span class="p">(),</span><span class="w"> </span><span class="mf">0.</span><span class="n">into</span><span class="p">()),</span><span class="w"> </span><span class="c1">// Un pointeur vers du contenu qui peut servir à stocker ce que vous voulez. (Rien en l’occurrence ici)</span>
<span class="w"> </span><span class="mf">0.</span><span class="n">into</span><span class="p">(),</span><span class="w"> </span><span class="c1">// Notre index n'est pas trié</span>
<span class="w"> </span><span class="n">entry_store_id</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">jubako</span>::<span class="n">Count</span><span class="p">(</span><span class="mi">3</span><span class="p">),</span><span class="w"> </span><span class="c1">// On a trois entrées</span>
<span class="w"> </span><span class="n">jubako</span>::<span class="n">Idx</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span><span class="w"> </span><span class="c1">// Et on commence à l'offset 0 dans le entry_store.</span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">directory_pack_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">directory_pack</span><span class="p">.</span><span class="n">finalize</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">content_pack_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">content_pack</span><span class="p">.</span><span class="n">finalize</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">manifest_creator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">jbk</span>::<span class="n">creator</span>::<span class="n">ManifestPackCreator</span>::<span class="n">new</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="s">"test.jbkm"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">VENDOR_ID</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">FreeData</span>::<span class="o"><</span><span class="n">U63</span><span class="o">></span>::<span class="n">clone_from_slice</span><span class="p">(</span><span class="o">&</span><span class="p">[</span><span class="mh">0x00</span><span class="p">;</span><span class="w"> </span><span class="mi">63</span><span class="p">]),</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">manifest_creator</span><span class="p">.</span><span class="n">add_pack</span><span class="p">(</span><span class="n">directory_pack_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">manifest_creator</span><span class="p">.</span><span class="n">add_pack</span><span class="p">(</span><span class="n">content_pack_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">manifest_creator</span><span class="p">.</span><span class="n">finalize</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Vous avez maintenant 3 fichiers "test.jbkm", "test.jbkc" et "test.jbkd".</span>
<span class="w"> </span><span class="c1">// N'en faisons qu'un seul</span>
<span class="w"> </span><span class="n">jbk</span>::<span class="n">concat</span><span class="p">(</span><span class="o">&</span><span class="p">[</span><span class="s">"test.jbkm"</span><span class="p">,</span><span class="w"> </span><span class="s">"test.jbkc"</span><span class="p">,</span><span class="w"> </span><span class="s">"test.jbkd"</span><span class="p">],</span><span class="w"> </span><span class="s">"test.jbk"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">// On a maintenant un 4ème fichier "test.jbk" qui contient les trois autres.</span>
<span class="w"> </span><span class="c1">// Un peu de lecture</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">container</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">jbk</span>::<span class="n">reader</span>::<span class="n">Container</span>::<span class="n">new</span><span class="p">(</span><span class="s">"test.jbkm"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w"> </span><span class="c1">// ou "test.jbkm"</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">directory</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">container</span><span class="p">.</span><span class="n">get_directory_pack</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">directory</span><span class="p">.</span><span class="n">get_index_from_name</span><span class="p">(</span><span class="s">"mon petit index a moi"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">resolver</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">directory</span><span class="p">.</span><span class="n">get_resolver</span><span class="p">();</span><span class="w"> </span><span class="c1">// C'est nécessaire pour retrouver les infos dans value_store</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">finder</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">index</span><span class="p">.</span><span class="n">get_finder</span><span class="p">(</span><span class="n">Rc</span>::<span class="n">clone</span><span class="p">(</span><span class="o">&</span><span class="n">resolver</span><span class="p">));</span><span class="w"> </span><span class="c1">// On va enfin pouvoir lire nos données.</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">entry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">finder</span><span class="p">.</span><span class="n">get_entry</span><span class="p">(</span><span class="n">jbk</span>::<span class="n">Idx</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">get_variant_id</span><span class="p">(),</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"> </span><span class="c1">// On a bien le variant 0</span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">resolver</span><span class="p">.</span><span class="n">resolve_to_vec</span><span class="p">(</span><span class="o">&</span><span class="n">entry</span><span class="p">.</span><span class="n">get_value</span><span class="p">(</span><span class="mf">0.</span><span class="n">into</span><span class="p">())</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nb">Vec</span>::<span class="n">from</span><span class="p">(</span><span class="s">"Super"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">resolver</span><span class="p">.</span><span class="n">resolve_to_unsigned</span><span class="p">(</span><span class="o">&</span><span class="n">entry</span><span class="p">.</span><span class="n">get_value</span><span class="p">(</span><span class="mf">1.</span><span class="n">into</span><span class="p">())</span><span class="o">?</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="mi">50</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">value_2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">entry</span><span class="p">.</span><span class="n">get_value</span><span class="p">(</span><span class="mf">2.</span><span class="n">into</span><span class="p">())</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">content_address</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">resolver</span><span class="p">.</span><span class="n">resolve_to_content</span><span class="p">(</span><span class="o">&</span><span class="n">value_2</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// On affiche le contenu sur la sortie standard</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">reader</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">container</span><span class="p">.</span><span class="n">get_reader</span><span class="p">(</span><span class="n">content_address</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">std</span>::<span class="n">io</span>::<span class="n">copy</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="n">reader</span><span class="p">.</span><span class="n">create_stream_all</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="n">std</span>::<span class="n">io</span>::<span class="n">stdout</span><span class="p">().</span><span class="n">lock</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">entry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">finder</span><span class="p">.</span><span class="n">get_entry</span><span class="p">(</span><span class="n">jbk</span>::<span class="n">Idx</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">get_variant_id</span><span class="p">(),</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"> </span><span class="c1">// On a bien le variant 1</span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">resolver</span><span class="p">.</span><span class="n">resolve_to_vec</span><span class="p">(</span><span class="o">&</span><span class="n">entry</span><span class="p">.</span><span class="n">get_value</span><span class="p">(</span><span class="mf">0.</span><span class="n">into</span><span class="p">())</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nb">Vec</span>::<span class="n">from</span><span class="p">(</span><span class="s">"Mega"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">resolver</span><span class="p">.</span><span class="n">resolve_to_unsigned</span><span class="p">(</span><span class="o">&</span><span class="n">entry</span><span class="p">.</span><span class="n">get_value</span><span class="p">(</span><span class="mf">1.</span><span class="n">into</span><span class="p">())</span><span class="o">?</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="mi">42</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="n">resolver</span><span class="p">.</span><span class="n">resolve_to_unsigned</span><span class="p">(</span><span class="o">&</span><span class="n">entry</span><span class="p">.</span><span class="n">get_value</span><span class="p">(</span><span class="mf">2.</span><span class="n">into</span><span class="p">())</span><span class="o">?</span><span class="p">),</span><span class="w"> </span><span class="mi">5</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">entry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">finder</span><span class="p">.</span><span class="n">get_entry</span><span class="p">(</span><span class="n">jbk</span>::<span class="n">Idx</span><span class="p">(</span><span class="mi">2</span><span class="p">))</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">get_variant_id</span><span class="p">(),</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"> </span><span class="c1">// On a bien le variant 1</span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">resolver</span><span class="p">.</span><span class="n">resolve_to_vec</span><span class="p">(</span><span class="o">&</span><span class="n">entry</span><span class="p">.</span><span class="n">get_value</span><span class="p">(</span><span class="mf">0.</span><span class="n">into</span><span class="p">())</span><span class="o">?</span><span class="p">)</span><span class="o">?</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nb">Vec</span>::<span class="n">from</span><span class="p">(</span><span class="s">"Hyper"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">resolver</span><span class="p">.</span><span class="n">resolve_to_unsigned</span><span class="p">(</span><span class="o">&</span><span class="n">entry</span><span class="p">.</span><span class="n">get_value</span><span class="p">(</span><span class="mf">1.</span><span class="n">into</span><span class="p">())</span><span class="o">?</span><span class="p">),</span><span class="w"></span>
<span class="w"> </span><span class="mi">45</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="n">resolver</span><span class="p">.</span><span class="n">resolve_to_unsigned</span><span class="p">(</span><span class="o">&</span><span class="n">entry</span><span class="p">.</span><span class="n">get_value</span><span class="p">(</span><span class="mf">2.</span><span class="n">into</span><span class="p">())</span><span class="o">?</span><span class="p">),</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span></code></pre>
<h2 id="toc-loutil-arx">L’outil Arx</h2>
<blockquote>
<blockquote>
<p>Jubako c’est beau, mais franchement une lib… t’as pas mieux ? Un vrai cas d’usage ?</p>
</blockquote>
<p>Si ! Et c’est Arx, un outil pour faire des archives de fichiers un peu comme Tar ou Zip. Et en plus, ça sert de démonstrateur pour Jubako.</p>
</blockquote>
<h3 id="toc-tar">Tar</h3>
<p>Petite digression sur tar. Surtout si vous ne savez pas comment est structurée une archive tar.<br>
Tar c’est vieux, très vieux (1979). Ça date d’une époque où les disquettes 3.5 étaient une révolution (elles apparaissent en 1982)</p>
<p>Une archive tar, c’est des entrées (header + contenu) mises bout à bout. C’est tout.<br>
Un tar.gz, c’est un tar compressé avec gzip. Voilà.</p>
<p>Un tar ça marche bien pour du « streaming ». On crée l’archive simplement en parcourant le dossier à archiver et écrivant les entrées dans un « flux » (la sortie standard par exemple) dès qu’on les lit. On passe le flux à gzip et voilà.<br>
Pour décompresser on fait l’inverse.<br>
Et, comme on compresse toute l’archive « par l’extérieur », c’est probablement là qu’on a les meilleurs ratios de compression.<br>
Par contre… accéder à une entrée en particulier… Ben il faut parcourir toute l’archive pour trouver l’entrée. Et pour parcourir toute l’archive, il faut la décompresser. Et ça prend du temps.</p>
<h3 id="toc-le-format-arx"> Le format Arx</h3>
<p>Il n’y a qu’un seul type d’entrée dans Arx et il est composé de trois variants : </p>
<ul>
<li>Fichier</li>
<li>Dossier</li>
<li>Lien symbolique</li>
</ul>
<p>Pour le moment, aucune méta-donnée sur les fichiers n’est stockée. Donc pas de owner, group, droit d’accès ou attributs étendus. Il vous faudra attendre un peu pour ça :) ARX utilise une structure en arbre. Chaque dossier pointe vers un « range » d’entrées qu’il contient. Chaque entrée (y compris les dossiers) contient l’id de son dossier parent. Le nom des entrées est le « basename ». On ne stocke pas tout le chemin de l’entrée.</p>
<p>Cette structure en arbre permet d’accélérer la recherche d’entrée puisqu’on n’a pas besoin de faire une recherche « linéaire » sur toutes les entrées. Cela permet aussi de gagner de la place puisqu’on ne stocke pas le chemin complet. En contrepartie, trouver le chemin complet à partir d’une entrée nécessite de remonter tous ses parents. Mais c’est un cas de figure assez rare.</p>
<p>L’outil <code>arx</code> est relativement simple et permet cinq opérations :</p>
<ol>
<li>créer une archive à partir d’un ou plusieurs dossiers/fichiers,</li>
<li>extraire une archive dans un dossier,</li>
<li>lister les entrées dans une archive,</li>
<li>dumper (j’ai pas de traduction française) une entrée d’une archive,</li>
<li>monter l’archive dans un point de montage.</li>
</ol>
<p>Le code d’arx est assez simple (pour le moment). Il y a sept fichiers qui ne dépassent pas les 500 lignes de code chacun. Je vous invite vivement à aller le voir.</p>
<h3 id="toc-un-peu-de-comparaison">Un peu de comparaison</h3>
<blockquote>
<p>Mais du coup, arx, ça vaut quoi par rapport à tar ?</p>
</blockquote>
<p>Faisons donc un peu de tests. Pour tester arx, j’ai utilisé les sources du kernel Linux (on est sur linuxfr ou pas ?). J’en ai fait trois cas de test différents:</p>
<ul>
<li>Les sources au complet</li>
<li>Le dossier <code>Documentation</code> seulement</li>
<li>Le dossier <code>drivers</code> seulement</li>
</ul>
<p>De plus, arx utilise <a href="https://fr.wikipedia.org/wiki/Zstandard">zstd</a> comme algo de compression. Donc pour éviter de comparer des pommes avec des poires, il faut comparer à un tar.zst, pas un tar.gz. Il y a bien une option chez tar pour compresser en utilisant zstd mais ça utilise pas la même config (niveau de compression) que arx, qui prend le parti de compresser au maximum au détriment du temps de compression. Du coup, l’archive tar.zst est faite avec : <code>tar c linux-5.19 | zstd -19 -T8 -o linux.tar.zst</code>. (niveau de compression 19, 8 threads).<br>
Enfin, pour le contexte de test, les sources à compresser sont sur un SSD mais tout le reste c’est du tmpfs. Ce qui limite les entrées/sorties au minimum (mais en vrai ça change pas les ordres de grandeurs).</p>
<p>Voici les chiffres bruts, l’analyse arrive après :</p>
<h4 id="toc-pour-la-partie-documentation-seulement-9194-entrées">Pour la partie documentation seulement (9194 entrées) :</h4>
<table>
<thead>
<tr>
<th></th>
<th>Taille</th>
<th>Création</th>
<th>Extraction</th>
<th>Listing</th>
<th>Dump</th>
<th>Dump / entry</th>
<th>Mount diff</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Source</strong></td>
<td>58 MB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>66ms</td>
</tr>
<tr>
<td><strong>Tar zstd</strong></td>
<td>7.8 MB</td>
<td>8s3</td>
<td>68ms</td>
<td>51ms</td>
<td>2m9s</td>
<td>43ms</td>
<td>2m38s</td>
</tr>
<tr>
<td><strong>Arx</strong></td>
<td>8.3 MB</td>
<td>8s7</td>
<td>100ms</td>
<td>5ms</td>
<td>8s9</td>
<td>3.4ms</td>
<td>324ms</td>
</tr>
<tr>
<td><strong><em>Ratios</em></strong></td>
<td><strong><em>1.06</em></strong></td>
<td><strong><em>1.05</em></strong></td>
<td><strong><em>1.47</em></strong></td>
<td><strong><em>0.1</em></strong></td>
<td><strong><em>0.07</em></strong></td>
<td></td>
<td><strong><em>0.002</em></strong></td>
</tr>
</tbody>
</table>
<h4 id="toc-pour-la-partie-drivers-seulement-33056-entrées">Pour la partie drivers seulement (33056 entrées) :</h4>
<table>
<thead>
<tr>
<th></th>
<th>Taille</th>
<th>Création</th>
<th>Extraction</th>
<th>Listing</th>
<th>Dump</th>
<th>Dump / entry</th>
<th>Mount diff</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Source</strong></td>
<td>865 MB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>490ms</td>
</tr>
<tr>
<td><strong>Tar zstd</strong></td>
<td>73 MB</td>
<td>1m7</td>
<td>688ms</td>
<td>570ms</td>
<td>1h36</td>
<td>520ms</td>
<td>2h41m</td>
</tr>
<tr>
<td><strong>Arx</strong></td>
<td>80 MB</td>
<td>3m25</td>
<td>930ms</td>
<td>19ms</td>
<td>35s</td>
<td>3.1ms</td>
<td>1s75</td>
</tr>
<tr>
<td><strong><em>Ratios</em></strong></td>
<td><strong><em>1.09</em></strong></td>
<td><strong><em>3</em></strong></td>
<td><strong><em>1.35</em></strong></td>
<td><strong><em>0.03</em></strong></td>
<td><strong><em>0.006</em></strong></td>
<td></td>
<td><strong><em>0.00018</em></strong></td>
</tr>
</tbody>
</table>
<h4 id="toc-et-pour-les-sources-complètes-81958-entrées">Et pour les sources complètes (81958 entrées) :</h4>
<table>
<thead>
<tr>
<th></th>
<th>Taille</th>
<th>Création</th>
<th>Extraction</th>
<th>Listing</th>
<th>Dump</th>
<th>Dump / entry</th>
<th>Mount diff</th>
<th>Compilation</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Source</strong></td>
<td>1326 MB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>880ms</td>
<td>32m</td>
</tr>
<tr>
<td><strong>Tar zstd</strong></td>
<td>129 MB</td>
<td>1m37s</td>
<td>1s130ms</td>
<td>900ms</td>
<td>6h20m</td>
<td>833ms</td>
<td></td>
<td></td>
</tr>
<tr>
<td><strong>Arx</strong></td>
<td>140 MB</td>
<td>4m45s</td>
<td>1s47ms</td>
<td>45ms</td>
<td>1m28s</td>
<td>4ms</td>
<td>4s2</td>
<td>48m</td>
</tr>
<tr>
<td><strong><em>Ratios</em></strong></td>
<td><strong><em>1.08</em></strong></td>
<td><strong><em>2.93</em></strong></td>
<td><strong><em>1.3</em></strong></td>
<td><strong><em>0.05</em></strong></td>
<td><strong><em>0.0045</em></strong></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h4 id="toc-la-taille">La taille</h4>
<ul>
<li>Les sources décompressées du kernel font 1,3 Go.</li>
<li>L’archive tar.zst fait 129 Mo. (Un ratio de compression de 9,76%).</li>
<li>L’archive arx elle fait 140 Mo. (Un ratio de compression de 10,56%).</li>
</ul>
<p>Arx compresse moins bien. On a environ 8 % d’écart entre les deux archives. On s’y attendait au vu des structures de chaque archive, mais perso je trouve pas ça dégueu. Surtout comparé au 1,3 Go d’origine. Et pour info, le tar.gz utilisé par tout le monde, fait 200 Mo et ça a pas l’air de gêner grand monde alors bon, 140 Mo, ça va.</p>
<h4 id="toc-le-temps-de-création">Le temps de création</h4>
<p>La création de l’archive kernel.tar.zst se fait en 1m37s alors que l’arx se fait en 4m45s. Sans surprise ici, tar est bien plus rapide. On a globalement un rapport de 3 entre les temps de création. C’est en accord avec le cas d’usage de Jubako (temps de création plus lent, mais exploitation plus rapide) mais la création est probablement la partie la moins optimisée pour le moment. Il devrait être possible d’améliorer les perfs du côté de Jubako pour limiter l’écart (notamment compresser les contenus en parallèle).</p>
<h4 id="toc-le-temps-dextraction">Le temps d’extraction</h4>
<p>Le temps d’extraction se fait dans des temps relativement similaires : 1s1 pour tar et 1s5 pour arx. Là aussi, c’est en accord avec la structure des données des deux formats. Tar n’a qu’un seul flux à décompresser. Alors que Jubako doit parcourir les entrées et doit ensuite décompresser plusieurs flux internes (avec tout le temps d’initialisation associé). Mais, là encore, il y a probablement matière à amélioration du côté de Jubako/arx. </p>
<h4 id="toc-le-temps-de-listing">Le temps de listing</h4>
<p>Ici on n’extrait pas le contenu, mais on liste les fichiers dans l’archive. Il faut 10 à 20 fois moins de temps à arx pour lister le contenu de l’archive par rapport à tar. Là aussi, c’est cohérent avec la structure de données des deux formats. Jubako n’a aucun contenu à décompresser et il n’a que le <code>directoryPack</code> à lire, alors que tar doit décompresser toute l’archive.</p>
<p>On commence à toucher à des cas d’usage pour lesquels Jubako a été conçu : accéder aux données sans extraire toute l’archive.</p>
<h4 id="toc-le-temps-de-dumping">Le temps de dumping</h4>
<p>Par dumping j’entends extraire un seul fichier en particulier d’une archive.<br>
Le cas de test est d’extraire un fichier sur trois de l’archive.<br>
Pour le kernel au complet, ça veut dire extraire 27319 fichiers de l’archive (et donc lancer 27319 fois tar/arx)</p>
<p>Il faut 1m30 à arx pour faire le travail. Du côté de tar il faut… 6h20.<br>
Ça fait une moyenne de 4ms par entrée du côté d'arx et 0,8s pour tar.<br>
C’est un ratio de <strong>200</strong> !!</p>
<p>Si on réduit la taille des archives (ce qui limite la quantité de données à décompresser pour tar), le ratio baisse un peu, mais on reste quand même avec arx 12 fois plus rapide que tar pour la documentation. (3064 fichiers à dumper)</p>
<p>On voit ici clairement l’avantage de Jubako sur tar. C’est prévisible, rien de magique, on travaille juste avec un format de fichier fait pour ça, c’est normal que ça dépote.</p>
<p>Cela met aussi en évidence le fait qu’arx met un temps relativement constant pour extraire un fichier, quelle que soit la taille de l’archive.<br>
À l’inverse pour tar, les temps d’extraction augmentent avec la taille des archives.</p>
<h4 id="toc-mount">Mount</h4>
<p>Un mount tout seul ne prend pas de temps, il faut voir quand on veut lire les fichiers. Le test correspond donc à un mount suivi d’un <code>diff -r</code> entre ce qui a été monté et les sources d’origine. C’est le temps de diff qui est mesuré. L’archive tar est montée avec l’outil <code>archivemount</code>.</p>
<p>Bon, là c’est clair, arx est bien bien bien plus rapide que tar : un diff sur une archive arx monté est de <strong>490</strong> à <strong>5500</strong> fois plus rapide qu’un diff sur une archive tar. (Je n’ai pas osé faire le test sur le kernel complet, mais je vous laisse le faire si vous voulez). On notera quand même que le diff prend quatre à cinq fois plus de temps qu’un diff simple entre deux dossiers (sans mount).</p>
<p>Mais diff, c’est un cas vachement particulier, on parcourt certes tous les fichiers, mais c’est assez séquentiel et on y accède qu’une seule fois. Ça donnerait quoi avec un vrai cas d’usage ?</p>
<p>Du coup, compilons un kernel…<br>
La compilation du kernel simplement (configuration par défaut, <code>make -j8</code>, les sources extraites dans le fs, sur sdd) prend 32 minutes sur ma machine. La même compilation mais sur une archive montée (archive elle-même stockée sur le sdd, pas dans tmpfs) prend 48 minutes. Alors oui, ça prend 1,5 fois plus de temps, mais sachez que l’implémentation actuelle de <code>arx mount</code> est mono-threadée, donc le <code>make -j8</code> en prend un coup de base. Mais vous n’avez utilisé que 140 Mo d’espace disque au lieu de 1,3Go pour stocker les sources du kernel.</p>
<h3 id="toc-utilisation-mémoire">Utilisation mémoire</h3>
<p>Niveau utilisation mémoire, Jubako est, normalement, relativement sobre.</p>
<p>La partie index (stockée dans le directoryPack) est directement exploitable par Jubako. Rien n’est compressé et Jubako est conçu pour lire directement les données stockées dans le fichier. Bien sûr, ça engendre beaucoup d’I/O et c’est donc au détriment des performances. Pour réduire ça, différentes stratégies sont utilisées (bufferisation, mmap, cache…). Mais dans un contexte vraiment limité en mémoire, c’est désactivable (sur le principe, il y a pas vraiment d’options aujourd’hui dans l’implémentation). De toute façon, le directoryPack de l’archive de l’ensemble des sources du kernel fait 2 Mo. Donc on pourrait tout mettre en ram sans que ça ne pose de problème.</p>
<p>Au niveau des données compressées, ça nécessite un peu plus de données. Les données sont regroupées en « clusters » et les clusters sont compressés indépendamment. Donc pour accéder à un contenu, il faut décompresser au pire un cluster entier. L’implémentation actuelle crée des clusters de 4 Mo maximum. Donc sans cache, accéder à un contenu couterait 4 Mo max (plus les structures internes utilisées par les algorithmes de (dé)compression).</p>
<p>En pratique, il y a du cache mis en place (et pas obligatoirement de la manière la plus optimisée). Jubako fait de la décompression partielle : il commence à décompresser un cluster jusqu’aux données auxquelles on veut accéder. Mais pour éviter de décompresser à nouveau le début du cluster plus tard, on garde en mémoire le contexte de décompression en mémoire. Donc chaque cluster a certes 4 Mo maximum, mais on garde plus en mémoire. Et actuellement, on a un cache de 20 clusters en mémoire. Donc environ 80 Mo plus les 20 contextes de décompression (et je n’ai pas mesuré leur taille).</p>
<p>Au global, un <code>arx mount</code> pendant une compilation de kernel consomme 310 Mo au maximum (Maximum resident size). Il y a un peu de travail d’implémentation et d’ajustement pour avoir le meilleur compromis mémoire utilisée/performance. Et probablement de la configuration nécessaire pour s’adapter aux différents cas d’usage.</p>
<h2 id="toc-conclusion">Conclusion</h2>
<p>Voilà pour une première présentation de Jubako et Arx.</p>
<p>On est loin d’avoir un produit fini. Les specs de Jubako ne sont pas complètes. L’implémentation n’implémente pas tout ce qui est spécifié (et la spec <s>risque de</s> va changer avec l’implémentation).<br>
La bibliothèque Jubako elle-même est encore très jeune. Elle fonctionne, mais elle n’est probablement pas exempte de bugs, l’api est à améliorer, sans parler des performances.</p>
<p>Pour ce qui est de Arx, là aussi on en est au début. Arx stocke très peu de métadonnées sur les fichiers pour le moment, mais c’est une base et elle sert de très bon démonstrateur pour Jubako.</p>
<p>La différence entre ce qui est perdu (taille de l’archive, temps de compression) et ce qui est gagné (utilisation d’une archive sans la décompresser entièrement) est plus que raisonnable pour moi (surtout pour une première version).</p>
<p>Il y a encore pas mal de choses à faire mais c’est un premier jalon important pour moi de voir un projet imaginé il y a maintenant deux ans prendre forme et arriver à un résultat assez probant. (J’ai d’ailleurs eu du mal à me restreindre à faire une dépêche courte, j’en suis désolé… ou presque).</p>
<p>Jubako et Arx peuvent avoir leur utilité dans des cas d’usage particuliers. Pour ce qui est de la classique archive de fichiers qui sera extraite, il est fort probable que tar reste la référence. Mais Jubako ouvre une porte sur de toutes nouvelles façons de faire. Il serait possible de diffuser du contenu et de le lire <em>sans</em> le décompresser. Imaginez un peu :</p>
<ul>
<li>Une distribution Linux basée sur Jubako pour ses paquets. Des paquets qui ne seraient jamais décompressés mais montés à la demande…</li>
<li>Un système de sauvegarde (une sauvegarde incrémentale ne serait qu’une archive Jubako qui réutilise le contentPack des sauvegardes précédentes)…</li>
<li>Si python savait lire des archives Jubako…</li>
<li>Si on diffusait nos sites web statiques à coup de conteneur Jubako (ou de serveurs autonomes qui contiennent des conteneurs Jubako sous forme de ressources intégrées) …</li>
<li>Si les navigateurs web savaient lire du Jubako et qu’on packageait nos applis JavaScript à coup de Jubako…</li>
</ul>
<p>Comment ça je m’emballe ?!</p>
</div><div><a href="https://linuxfr.org/news/jubako-et-arx-un-conteneur-universel-et-son-format-d-archive.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/129067/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/jubako-et-arx-un-conteneur-universel-et-son-format-d-archive#comments">ouvrir dans le navigateur</a>
</p>
GaMaorfenorYves Bourguignonvmagninpalm123Ysabeau 🧶 🧦patrick_gLtrlgJulien Jorgehttps://linuxfr.org/nodes/129067/comments.atomtag:linuxfr.org,2005:News/411602022-08-31T16:20:39+02:002022-08-31T16:20:39+02:00Oubliez les web services, utilisez des tubes nommésLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Programmer des services web (ou n'importe quel middleware) est pénible et inutile dans la plupart des cas, alors que Linux propose de quoi faire communiquer des programmes en lisant simplement des fichiers.</p>
<p>Cette dépêche vous montre comment.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://github.com/nojhan/named-pipes-services" hreflang="en" href="https://linuxfr.org/redirect/110958">Plus d'exemples en Python et C++</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-introduction">Introduction</a><ul>
<li><a href="#toc-justification">Justification</a></li>
<li><a href="#toc-vue-densemble">Vue d'ensemble</a></li>
<li><a href="#toc-principe">Principe</a></li>
</ul>
</li>
<li>
<a href="#toc-exemples">Exemples</a><ul>
<li><a href="#toc-exemple-trivial--un-service-de-chat">Exemple trivial : un service de chat</a></li>
<li><a href="#toc-un-service-simple">Un service simple</a></li>
</ul>
</li>
<li><a href="#toc-exposer-ces-services-sur-le-r%C3%A9seau">Exposer ces services sur le réseau</a></li>
</ul>
<h2 id="toc-introduction">Introduction</h2>
<h3 id="toc-justification">Justification</h3>
<p>Le problème consistant à faire <em>communiquer</em> deux programmes est l'un de ceux qui ont généré la plus grande littérature et le plus grand code de toute l'informatique (avec l'invalidation de cache, nommer des choses et les frameworks Web).</p>
<p>Face à un tel problème, un programmeur pense immédiatement « je vais utiliser un middleware ». Si vous ne savez pas vraiment ce qu'est un middleware, rassurez-vous, personne ne le sait vraiment. Aujourd'hui, vous avez peut-être entendu parler de leur dernier avatar : les <em>services web</em>. Comme notre programmeur va s'en rendre compte, on a maintenant <em>deux</em> problèmes. La charge d'écriture, d'utilisation et de maintenance du code utilisant des middlewares est toujours énorme.</p>
<p>Parce qu'ils sont conçus pour gérer un <strong>très grand</strong> nombre de fonctionnalités et de situations complexes - la plupart d'entre elles impliquant des utilisateurs adverses, des utilisateurs robots, ou les deux.</p>
<p>Mais la plupart du temps, le problème réel n'implique pas vraiment ces situations. Du moins pas au début (… ce qui signifie probablement jamais). Les personnes connaissant l'histoire des middlewares diront que leur caractéristique principale n'est pas seulement le <em>passage de messages</em>, mais aussi l'<em>appel à distance</em>, qui implique la <em>sérialisation des objets</em>. Mais la plupart du temps, les messages sont assez simples de toute façon, et utiliser un middleware pour implémenter la sérialisation d'une liste d'instances ayant trois membres de types fondamentaux n'est pas une bonne utilisation de votre temps.</p>
<p>Si vous construisez des (premières versions de) programmes communicants qui fonctionneront sur un réseau local (sûr), et pour lesquels les messages échangés sont connus et simples, alors j'ai une bonne nouvelle : <strong>vous n'avez pas besoin d'utiliser des services web</strong> (ou tout autre type de middleware).</p>
<p><strong>VOUS DEVEZ JUSTE SAVOIR COMMENT LIRE/ÉCRIRE DEPUIS/VERS DES FICHIERS (SPÉCIAUX)</strong>.</p>
<h3 id="toc-vue-densemble">Vue d'ensemble</h3>
<p>L'idée de base est qu'au lieu de programmer l'interface réseau de votre service avec des sockets de bas niveau ou toute autre bibliothèque de haut niveau, vous pouvez simplement mettre en œuvre des mécanismes de requête/réponse en utilisant des <strong>tubes nommés</strong>.</p>
<p>Les tubes nommés sont des fichiers spéciaux <em>FIFO</em> (First In, First Out) qui bloquent les E/S et mettent en œuvre une forme très basique de passage de messages, sans avoir à s'embarrasser avec le polling. De plus, ils sont très faciles à utiliser, puisqu'il s'agit simplement de fichiers dans lesquels vous lisez/écrivez.</p>
<p>Une fois que vous avez créé votre service au-dessus de ces tubes, il est facile de l'intégrer dans une interface réalisée avec d'autres langages/outils. Par exemple, il est très facile de l'exposer sur le réseau en utilisant des outils courants comme <code>socat</code>.</p>
<p>Attention, ce n'est pas sécurisé, vous ne devez l'utiliser qu'à des fins de test dans un réseau local sûr.</p>
<h3 id="toc-principe">Principe</h3>
<p>Le principe théorique peut être représenté par ce diagramme de séquence UML :</p>
<pre><code> Tubes nommés
┌────────┴────────┐
┌──────┐ ┌──────┐ ┌──────┐ ┌───────┐
│Client│ │SORTIE│ │ENTRÉE│ │Service│
└───┬──┘ └─┬────┘ └────┬─┘ └───┬───┘
│ │ │ │
│ │ │┌───────╢
│ │ bloque││attente║
│requête │ │└──────→║
├─────────────────────→│ │
╟───────┐│ ├───────→│
║attente││bloque │ ║traitement
║←──────┘│ │ ║
│ │←─────────────────────┤
│←───────┤ │ réponse│
│ │ │ │
</code></pre>
<p>Notes :<br>
- Le service est démarré en premier et attend l'entrée, mais comme les processus sont bloquants, l'ordre de démarrage n'a pas toujours d'importance.<br>
- Il y a deux tuyaux, ici (un pour l'entrée et un pour la sortie), pour des raisons de simplicité, mais vous pouvez tout aussi bien n'en utiliser qu'un seul.</p>
<h2 id="toc-exemples">Exemples</h2>
<p>Pour créer les tuyaux nommés sous Linux ou MacOS, utilisez la commande <code>mkfifo</code>, comme indiqué dans le script <code>build.sh.</code></p>
<p>La création de tuyaux nommés sous Windows est plus complexe, vous pouvez consulter la <a href="https://stackoverflow.com/questions/3670039/is-it-possible-to-open-a-named-pipe-with-command-line-in-windows">question Stack Overflow correspondante.</a></p>
<h3 id="toc-exemple-trivial--un-service-de-chat">Exemple trivial : un service de chat</h3>
<p>L'exécutable <code>pcat</code> implémente un service qui lit depuis un tuyau nommé et imprime son contenu sur la sortie standard. C'est comme une commande <code>cat</code>, mais qui ne s'arrêterait pas après la première lecture, mais continuerait à lire depuis le tube.</p>
<p>Ce type de service est juste une simple boucle qui itère sur les appels d'E/S bloquants sur les tuyaux nommés, ayant ainsi un coût CPU nul pour l'interrogation.</p>
<p>Remarque : si cet exemple imprime « Hello World ! » en continu, c'est parce que vous n'avez pas créé le fichier de <code>données</code> comme un tube nommé, mais comme un fichier normal. Par conséquent, au lieu de vider son contenu après la lecture, il continue à lire le même contenu.</p>
<pre><code class="python"><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="k">as</span> <span class="n">fin</span><span class="p">:</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">fin</span><span class="o">.</span><span class="n">readline</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">line</span><span class="p">)</span></code></pre>
<h3 id="toc-un-service-simple">Un service simple</h3>
<p>Le premier exemple <code>./service in out</code> implémente un service qui lit depuis un tuyau nommé <code>in</code> et écrit vers un autre tuyau <code>out</code>.</p>
<p>Une fois lancé, le service va attendre que les tuyaux soient consommés, par exemple avec deux commandes. La première écrit une entrée dans le tuyau d'entrée :</p>
<pre><code class="sh"> <span class="nb">echo</span> <span class="s2">"données"</span> > in</code></pre>
<p>Le second lit le résultat :</p>
<pre><code class="sh"> cat out</code></pre>
<p>Notez que vous pouvez utiliser le même tuyau pour l'entrée et la sortie<br>
: <code>./service1 data data</code>.</p>
<pre><code class="python"><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"Start server"</span><span class="p">)</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="k">as</span> <span class="n">fin</span><span class="p">:</span>
<span class="n">datas</span> <span class="o">=</span> <span class="n">fin</span><span class="o">.</span><span class="n">readline</span><span class="p">()</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">datas</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"Received: <"</span><span class="p">,</span><span class="n">data</span><span class="p">,</span><span class="s2">">"</span><span class="p">,</span> <span class="nb">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="s1">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">fout</span><span class="p">:</span>
<span class="n">fout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">data</span> <span class="o">==</span> <span class="s2">"exit"</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">print</span><span class="p">(</span><span class="s2">"Stop server"</span><span class="p">,</span> <span class="nb">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span></code></pre>
<h2 id="toc-exposer-ces-services-sur-le-réseau">Exposer ces services sur le réseau</h2>
<p>Si vous souhaitez exposer un tel service en tant que serveur réseau, utilisez simplement socat.</p>
<p>Par exemple, pour obtenir une requête de <em>données</em> du réseau pour le <code>service1</code>:</p>
<pre><code class="sh"> socat -v -u TCP-LISTEN:8423,reuseaddr PIPE :./data</code></pre>
<p>(voir <code>run_socat_server.sh</code> pour un exemple complet).</p>
<p>Vous pouvez le tester en envoyant quelque chose sur la connexion :</p>
<pre><code class="sh"> <span class="nb">echo</span> <span class="s2">"Hello World !"</span> > /dev/tcp/127.0.0.1/8423</code></pre>
<p>Inversement, pour renvoyer automatiquement la réponse à un serveur :</p>
<pre><code class="sh"> socat -v -u PIPE :./out TCP2:8424:host</code></pre>
<p>Sachez que <code>socat</code> se termine dès qu'il reçoit la fin du message. <br>
Ainsi, si vous souhaitez établir un portail permanent, vous devrez utiliser l'option <code>fork</code>:</p>
<pre><code class="sh"> socat TCP-LISTEN:8478,reuseaddr,fork PIPE:/./data</code></pre>
</div><div><a href="https://linuxfr.org/news/oubliez-les-web-services-utilisez-des-tubes-nommes.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/128619/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/oubliez-les-web-services-utilisez-des-tubes-nommes#comments">ouvrir dans le navigateur</a>
</p>
nojhanXavier TeyssierPierre JarillonJulien Jorgehttps://linuxfr.org/nodes/128619/comments.atomtag:linuxfr.org,2005:News/410302022-05-30T11:26:15+02:002022-05-30T23:45:58+02:00Compiler Explorer a 10 ansLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Matt Godbolt, l'auteur originel de <a href="https://godbolt.org/">Compiler Explorer</a> nous apprend sur <a href="https://xania.org/202206/happy-birthday-ce">son blog</a> que l'outil a atteint 10 ans le 22 mai 2022.</p>
<p>Compiler Explorer est un site web sur lequel l'utilisateur peut écrire un programme et observer l'assembleur généré par le compilateur. Il s'agit d'un logiciel libre, écrit en JavaScript, et disponible sous les termes du contrat BSD-2-clause.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://godbolt.org/" hreflang="en" href="https://linuxfr.org/redirect/110497">Compiler Explorer</a></li><li>lien nᵒ 2 : <a title="https://xania.org/202206/happy-birthday-ce" hreflang="en" href="https://linuxfr.org/redirect/110498">Le post sur le blog de l'auteur</a></li><li>lien nᵒ 3 : <a title="https://github.com/compiler-explorer/compiler-explorer" hreflang="en" href="https://linuxfr.org/redirect/110499">Code source</a></li><li>lien nᵒ 4 : <a title="https://www.youtube.com/watch?v=kIoZDUd5DKw" hreflang="en" href="https://linuxfr.org/redirect/110500">Une présentation de Compiler Explorer par Matt Godbolt à CppCon 2019</a></li></ul><div><p>Il s'agissait initialement d'un petit outil développé sur son temps libre pour trouver des réponses à des questions d'optimisations réalisées par le compilateur. Depuis, le service est toujours le même, à ceci près qu'il répond maintenant aux questions de milliers de développeurs sur de nombreux langages et compilateurs !</p>
<p>Entre temps l'outil a évolué et est capable d'afficher l'assembleur généré par différents compilateurs (GCC et Clang bien sûr, mais aussi ICC, ICX, MSVC et plein d'autres), et dans plusieurs versions. Il sait même afficher l'IR LLVM quand <code>-emit-llvm</code> est passé à Clang.</p>
<p>Le site s'est aussi étendu vers le support de plus de trente autres langages. On y trouve du C et du C++, mais aussi du Java, Pascal, Kotlin, Rust, Python…</p>
<p>Tout cela n'aurait pu être possible sans de nombreux contributeurs et soutiens, que Matt n'oublie pas de nommer dans son post :</p>
<ul>
<li>Partouf, développeur et administrateur du site ;</li>
<li>Rubén, un des premiers gros contributeurs ;</li>
<li>Jason Turner, qui a grandement participé à la popularisation de Compiler Explorer en l'utilisant dans ses présentations ;</li>
<li>Austin, un autre administrateur qui s'occupe aussi de la sécurité du site et du bac à sable ;</li>
<li>Mats, Marc et Jeremy, les plus récents commiteurs ;</li>
<li>Nicole, Tim et Dale de Microsoft pour avoir ajouté le support des compilateurs basés sur Windows ;</li>
<li>Luka et Chedy pour l'amitié, les conversations et les conseils durant ces années ;</li>
<li>et enfin sa femme et ses enfants pour apporter leur soutien et accepter qu'il soit occupé avec son loisir bizarre.</li>
</ul>
</div><div><a href="https://linuxfr.org/news/compiler-explorer-a-10-ans.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/127832/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/compiler-explorer-a-10-ans#comments">ouvrir dans le navigateur</a>
</p>
Julien JorgeBenoît SibaudXavier Teyssierhttps://linuxfr.org/nodes/127832/comments.atomtag:linuxfr.org,2005:News/406122021-08-13T18:36:32+02:002021-08-13T18:36:32+02:00Débuter avec SolveSpaceLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Il y a dix ans, je disposais d’un peu de temps pour tester divers modeleurs volumiques (fonctionnant sous Linux). J’en ai alors profité pour rédiger <a href="http://eleydet.free.fr/CAO.html">quelques articles</a>.</p>
<p>À cette époque, je voulais aussi étudier SolveSpace, mais j’ai dû passer à autre chose.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f736f6c766573706163652e636f6d2f706963732f65782d636f6c6c65742e6a7067/ex-collet.jpg" alt="Une pièce dessinée avec SoleSpace" title="Source : https://solvespace.com/pics/ex-collet.jpg"></p>
<p>SolveSpace est un logiciel de CAO 3D original dans son maniement, plutôt agréable et performant, avec un peu d’habitude. Il fonctionne sous Linux, Windows ou MacOS… et sous d’autres systèmes d’exploitation. Il est disponible en anglais. Il s’appuie sur deux fenêtres :</p>
<ul>
<li>une pour la visualisation en trois dimensions ;</li>
<li>et l’autre pour les informations sur le modèle volumique.</li>
</ul>
</div><ul><li>lien nᵒ 1 : <a title="http://eleydet.free.fr/CAO.html" hreflang="fr" href="https://linuxfr.org/redirect/108981">Le site de l’auteur et ses articles sur la CAO 3D </a></li><li>lien nᵒ 2 : <a title="https://solvespace.com/ref.pl" hreflang="en" href="https://linuxfr.org/redirect/108982">Manuel de référence de SolveSpace</a></li><li>lien nᵒ 3 : <a title="https://solvespacefr.weebly.com/manuel.html" hreflang="fr" href="https://linuxfr.org/redirect/108983">Manuel traduit en français</a></li><li>lien nᵒ 4 : <a title="https://solvespacefr.weebly.com/tutoriels.html" hreflang="fr" href="https://linuxfr.org/redirect/108984">Tutoriels</a></li></ul><div><p>Voilà ce à quoi il ressemble, après l’avoir lancé et ouvert un assemblage de pièces :</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726530302e706e67/capture00.png" alt="Assembler des pièces dans Solvespace" title="Source : http://eleydet.free.fr/CAO/solvespace/capture00.png"><br>
SolveSpace est un logiciel libre, sous licence GPLv3.</p>
<h2 id="toc-installer-solvespace">Installer SolveSpace</h2>
<p>L’installation s’effectue simplement en tapant la commande : <code>sudo apt install solvespace</code></p>
<h2 id="toc-ouvrir-et-visualiser-une-pièce">Ouvrir et visualiser une pièce</h2>
<p>Sélectionnez le menu <code>File -> Open</code>. Une fenêtre s’affiche alors pour choisir le dossier et la pièce à afficher. Un fichier avec l’extension .slvs doit être retenu.<br>
<img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726530312e706e67/capture01.png" alt="Ouvrir une pièce" title="Source : http://eleydet.free.fr/CAO/solvespace/capture01.png"></p>
<p>Une fois la pièce ouverte, vous pouvez la faire bouger dans tous les sens, pour la visualiser.</p>
<h3 id="toc-pour-tourner-la-pièce-deux-solutions-">Pour tourner la pièce, deux solutions :</h3>
<ul>
<li>maintenez le bouton central de la souris enfoncé et déplacez la souris ;</li>
<li>maintenez la touche <code>Majuscule</code> et le bouton droit de la souris enfoncé et déplacez la souris. </li>
</ul>
<h3 id="toc-pour-déplacer-la-pièce-">Pour déplacer la pièce :</h3>
<ul>
<li>maintenez le bouton droit de la souris enfoncé et déplacez la souris.</li>
</ul>
<h3 id="toc-pour-rapprocher-ou-éloigner-la-pièce-">Pour rapprocher ou éloigner la pièce :</h3>
<ul>
<li>appuyez sur les touches <code>+</code> ou <code>-</code> du clavier. </li>
</ul>
<p>Plusieurs exemples de pièces, au format .slvs, sont téléchargeables sur le site : <a href="https://solvespace.com/examples.pl">https://solvespace.com/examples.pl</a>.<br>
Des assemblages de pièces, au format .zip, sont également proposés. Dans ce cas, il faut au préalable extraire les fichiers de l’archive.</p>
<h2 id="toc-modéliser-une-pièce">Modéliser une pièce</h2>
<p>On sélectionne un plan, on y dessine un contour fermé, on le translate pour générer de la matière. Ensuite, on sélectionne une face du modèle, on y dessine un cercle pour percer un trou. Au lancement du logiciel, XY est le plan par défaut. Nous en choisissons un autre… juste pour montrer la procédure.</p>
<p>Sélectionnez dans la zone graphique le plan d’esquisse YZ, qui apparaît en rouge, puis le menu Sketch -> In Workplane.</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531302e706e67/capture10.png" alt="Modélisation d’une pièce" title="Source : http://eleydet.free.fr/CAO/solvespace/capture10.png"></p>
<p>Avec l’outil ligne, dessinez le polygone. On peut rendre une ligne horizontale ou verticale :</p>
<ul>
<li>en sélectionnant l’arête qui devient alors rouge ;</li>
<li>puis en allant dans le menu <code>Constrain</code>. </li>
</ul>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531312e706e67/capture11.png" alt="Dessin des lignes" title="Source : http://eleydet.free.fr/CAO/solvespace/capture11.png"></p>
<p>Les cotes dimensionnelles ne posent aucune difficulté. Il faut sélectionner :</p>
<ul>
<li>les deux extrémités d’une arête (distance entre deux points) ;</li>
<li>ou bien un point et une arête (distance d’un point à une droite). </li>
</ul>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531322e706e67/capture12.png" alt="Ajustement de la distance" title="Source : http://eleydet.free.fr/CAO/solvespace/capture12.png"></p>
<p>Le résultat à atteindre :</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531332e706e67/capture13.png" alt="Le résultat" title="Source : http://eleydet.free.fr/CAO/solvespace/capture13.png"></p>
<p>Ci-dessous, pour réaliser l’extrusion :</p>
<ul>
<li>allez dans le menu <code>New Group -> Extrude</code> pour créer de la matière par translation du contour fermé ;</li>
<li>cochez le bouton <code>tow-sided</code>, dans la fenêtre de droite, pour générer la matière de part et d’autre du plan d’esquisse ;</li>
<li>placez la cote de 60.00, à l'aide du menu <code>Constrain</code> comme précédemment. </li>
</ul>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531342e706e67/capture14.png" alt="Visualisation en volume" title="Source : http://eleydet.free.fr/CAO/solvespace/capture14.png"></p>
<p>Le nouveau plan d’esquisse se définit :</p>
<ul>
<li>en sélectionnant un point et deux arêtes, qui deviennent rouges ;</li>
<li>ensuite avec le menu <code>New Group -> Sketch in New Workplane</code>.</li>
</ul>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531352e706e67/capture15.png" alt="Modification du plan d’esquisse" title="Source : http://eleydet.free.fr/CAO/solvespace/capture15.png"></p>
<p>La nouvelle esquisse, un simple cercle :</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531362e706e67/capture16.png" alt="Dessin du cercle" title="Source : http://eleydet.free.fr/CAO/solvespace/capture16.png"></p>
<p>Pour produire le perçage :</p>
<ul>
<li>allez dans le menu <code>New Group -> Extrude</code> ;</li>
<li>cochez le bouton <code>difference</code> dans la fenêtre de droite. </li>
</ul>
<p>Notez que la couleur et l’opacité de la pièce se spécifient juste en dessous de ce bouton.</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531372e706e67/capture17.png" alt="Le volume avec le trou causé par la cercle" title="Source : http://eleydet.free.fr/CAO/solvespace/capture17.png"></p>
<p>Et voilà le résultat ! Pour obtenir une belle vue dénuée des éléments de construction :</p>
<ul>
<li>sélectionnez <code>home</code> dans la fenêtre de droite ;</li>
<li>décochez toutes les cases <code>shown</code>. </li>
</ul>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531382e706e67/capture18.png" alt="Vue du volume en perspective" title="Source : http://eleydet.free.fr/CAO/solvespace/capture18.png"></p>
<p>À tout moment, il est possible de retourner à une étape précédente pour une modification. Ici, pour changer une valeur, on est revenu à la première esquisse, en cochant le premier bouton de la colonne active, dans la fenêtre de droite.</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f656c65796465742e667265652e66722f43414f2f736f6c766573706163652f6361707475726531392e706e67/capture19.png" alt="Modification d’une valeur" title="Source : http://eleydet.free.fr/CAO/solvespace/capture19.png"></p>
<h2 id="toc-conclusion">Conclusion</h2>
<p>Comme pour tous les modeleurs volumiques, apprendre à manipuler SolveSpace nécessite d’y consacrer un peu de temps. Comme il n’est pas très intuitif au premier abord, l’étude du manuel s’avère indispensable… du moins au début. Après, lorsqu’on a compris la logique du logiciel, la modélisation devient aisée.</p>
<p>Le manuel de référence, en anglais, se trouve à l’adresse : <a href="https://solvespace.com/ref.pl">https://solvespace.com/ref.pl</a></p>
<p>Par bonheur, une version du manuel traduite en français existe. Plusieurs tutoriels la complètent, montrant clairement et précisément comment s’y prendre pour modéliser des pièces : <a href="https://solvespacefr.weebly.com/manuel.html">https://solvespacefr.weebly.com/manuel.html</a></p>
<p>SolveSpace est une magnifique et surprenante réalisation, sympathique et ergonomique. Elle mériterait d’être mieux connue.</p>
</div><div><a href="https://linuxfr.org/news/debuter-avec-solvespace.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/125125/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/debuter-avec-solvespace#comments">ouvrir dans le navigateur</a>
</p>
eleydetYsabeau 🧶 🧦Benoît Sibaudhttps://linuxfr.org/nodes/125125/comments.atomtag:linuxfr.org,2005:News/404822021-05-19T08:34:16+02:002022-10-16T21:20:53+02:00LSP (Language Server Protocol)Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p><abbr title="Note de la modération">N. D. M. :</abbr> dépêche réécrite en octobre 2022 suite à la demande de purge du compte de l'auteur initial</p>
<p><a href="https://en.wikipedia.org/wiki/Language_Server_Protocol">LSP</a> (Language Server Protocol) est un protocole ouvert basé sur <a href="https://en.wikipedia.org/wiki/JSON-RPC">JSON-RPC</a> pour la communication entre le logiciel éditeur / <a href="https://fr.wikipedia.org/wiki/Environnement_de_d%C3%A9veloppement">IDE</a> et un serveur qui lui fournit les informations sur un langage spécifique.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://microsoft.github.io/language-server-protocol/" hreflang="en" href="https://linuxfr.org/redirect/108520">Documentation (GitHub Microsoft)</a></li><li>lien nᵒ 2 : <a title="https://emacs-lsp.github.io/" hreflang="en" href="https://linuxfr.org/redirect/108524">LSP / Emacs </a></li><li>lien nᵒ 3 : <a title="https://bluz71.github.io/2019/10/16/lsp-in-vim-with-the-lsc-plugin.html" hreflang="en" href="https://linuxfr.org/redirect/108526">LSP / Vim</a></li><li>lien nᵒ 4 : <a title="https://langserver.org/" hreflang="en" href="https://linuxfr.org/redirect/108564">Langserver.org: a community-driven source of knowledge for Language Server Protocol implementations</a></li></ul><div></div><div><a href="https://linuxfr.org/news/lsp-language-server-protocol.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/124289/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/lsp-language-server-protocol#comments">ouvrir dans le navigateur</a>
</p>
Benoît Sibaudhttps://linuxfr.org/nodes/124289/comments.atomtag:linuxfr.org,2005:News/404212021-04-15T10:03:12+02:002021-04-15T10:03:12+02:00ReachOut test{fest} : les développeurs gagnent à tester des logiciels européens innovantsLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Jusqu’au 13 juin 2021, l’association open source <a href="http://www.ow2.org">OW2</a> et le projet européen <a href="http://www.reachout-project.eu">ReachOut</a> encouragent les développeurs à évaluer autant de logiciels innovants qu’ils le souhaitent et à partager leurs impressions à l’occasion d'une test{fest}.</p>
<p>En échange de leurs efforts, ils pourront interagir avec les concepteurs et recevoir des récompenses monétaires pouvant aller jusqu’à 30, 60 ou 90 euros par évaluation.</p>
<p>D'autres récompenses et certificats sont prévus dont un prix spécial ReachOut remis lors d’OW2con’21, la conférence annuelle d’OW2 qui aura lieu en ligne, les 23 et 24 juin 2021.</p>
<p>Pour participer à distance jusqu'au 13 juin 2021, les testeurs doivent suivre les <a href="https://l.ow2.org/testfest">instructions de la ReachOut test{fest}</a> en ligne.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://l.ow2.org/testfest" hreflang="en" href="https://linuxfr.org/redirect/108251">Instructions test{fest}</a></li><li>lien nᵒ 2 : <a title="https://www.ow2con.org" hreflang="en" href="https://linuxfr.org/redirect/108252">Conférence OW2con'21</a></li></ul><div><p>Le profil des participants et la configuration requise varient selon les campagnes de tests proposées, du simple utilisateur de smartphone au développeur débutant ou avancé, en passant par le testeur professionnel. </p>
<p>Chaque personne ayant un intérêt marqué pour l’innovation numérique peut participer ; elle doit simplement suivre les étapes proposées en ligne, puis remplir complètement un formulaire d’évaluation portant sur l’interface utilisateur, les fonctionnalités en place, manquantes ou les difficultés rencontrées.</p>
<p><strong>À propos de ReachOut</strong><br>
ReachOut est une action de coordination et de soutien financée par la Commission européenne dans le cadre du programme Horizon 2020, coordonnée par Fraunhofer FOKUS – la plus grande organisation de recherche appliquée en Europe, ainsi que UshareSoft et OW2. ReachOut agit comme un intermédiaire opérationnel entre les concepteurs de logiciels et les premiers utilisateurs. ReachOut aide les PME et les projets de recherche à mettre en œuvre leur campagne de bêta-test grâce à un packaging logiciel approprié, à un scénario de test efficace et à un questionnaire personnalisé.</p>
<p><strong>À propos d’OW2</strong><br>
OW2 est une communauté informatique indépendante dédiée à la promotion du logiciel open source pour les systèmes d’information et à l’animation de leurs écosystèmes. OW2 héberge une cinquantaine de projets technologiques dont ADR App, ASM, AuthzForce, CLIF, DocDoku, FusionDirectory, GLPI, JORAM, Knowage, LemonLDAP:NG, Lutece, OCS Inventory, Petals ESB, Prelude, ProActive, Rocket.Chat, SAT4J, SeedStack, Sympa, Telosys, Waarp, WebLab and XWiki. Visitez <a href="https://www.ow2.org/">www.ow2.org</a>, suivez-nous sur Twitter <a href="https://twitter.com/ow2">@ow2</a>.</p>
</div><div><a href="https://linuxfr.org/news/reachout-test-fest-les-developpeurs-gagnent-a-tester-des-logiciels-europeens-innovants.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/123950/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/reachout-test-fest-les-developpeurs-gagnent-a-tester-des-logiciels-europeens-innovants#comments">ouvrir dans le navigateur</a>
</p>
obJulien Jorgeclaudexhttps://linuxfr.org/nodes/123950/comments.atomtag:linuxfr.org,2005:News/403692021-04-04T13:01:34+02:002021-04-04T18:22:14+02:00FreeCAD 0.19Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>FreeCAD est un logiciel de CAO en 3D, c’est‐à‐dire de <a href="https://fr.wikipedia.org/wiki/Conception_assist%C3%A9e_par_ordinateur">conception assistée par ordinateur</a> (en anglais, CAD — Computer‐Aided Design), de type <a href="https://en.wikipedia.org/wiki/Solid_modeling#Parametric_and_feature-based_modeling">paramétrique</a>. Totalement libre, sous licence LGPL, disponible sous Linux, Windows et Mac, FreeCAD est destiné à un vaste public, de l’ingénieur concepteur en mécanique à l’utilisateur d’une imprimante 3D désirant concevoir une pièce, en passant par l’architecte en bâtiment.</p>
<p>La nouvelle version 0.19 est sortie ce 20 mars 2021.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f77696b692e667265656361647765622e6f72672f696d616765732f7468756d622f642f64392f467265654341445f686967686c696768745f315f302e31392e6a70672f33303170782d467265654341445f686967686c696768745f315f302e31392e6a7067/301px-FreeCAD_highlight_1_0.19.jpg" alt="Capture de FreeCAD 0.19" title="Source : https://wiki.freecadweb.org/images/thumb/d/d9/FreeCAD_highlight_1_0.19.jpg/301px-FreeCAD_highlight_1_0.19.jpg"><br>
CC-BY Leslie Fowl</p>
</div><ul><li>lien nᵒ 1 : <a title="https://www.freecadweb.org/?lang=fr/" hreflang="fr" href="https://linuxfr.org/redirect/108115">Le site officiel</a></li><li>lien nᵒ 2 : <a title="https://wiki.freecadweb.org/Release_notes_0.19/fr" hreflang="fr" href="https://linuxfr.org/redirect/108116">Les notes de version 0.19</a></li><li>lien nᵒ 3 : <a title="https://forum.freecadweb.org/viewforum.php?f=12" hreflang="fr" href="https://linuxfr.org/redirect/108117">Le forum officiel français</a></li><li>lien nᵒ 4 : <a title="https://wiki.freecadweb.org/Main_Page/fr" hreflang="fr" href="https://linuxfr.org/redirect/108118">Le wiki officiel francais</a></li><li>lien nᵒ 5 : <a title="http://help-freecad-jpg87.fr/00fr/index.php" hreflang="fr" href="https://linuxfr.org/redirect/108119">Un site d'initiation et tutoriel</a></li><li>lien nᵒ 6 : <a title="https://www.youtube.com/channel/UCoe3BcVuLC9I2_yFud5vp8Q/videos" hreflang="en" href="https://linuxfr.org/redirect/108120">Tutos vidéos YT</a></li><li>lien nᵒ 7 : <a title="https://www.youtube.com/channel/UCJwHW5GwrK1fq16cxUoBOUw/videos" hreflang="fr" href="https://linuxfr.org/redirect/108121">Tutos vidéos YT</a></li><li>lien nᵒ 8 : <a title="https://www.youtube.com/channel/UCheEfi_doRW1xwPaTeH8KtA/videos" hreflang="fr" href="https://linuxfr.org/redirect/108122">Tutos vidéos YT</a></li><li>lien nᵒ 9 : <a title="https://wiki.freecadweb.org/Feature_list/fr" hreflang="fr" href="https://linuxfr.org/redirect/108123">Liste des fonctionnalité de FreeCAD</a></li><li>lien nᵒ 10 : <a title="https://www.freecadweb.org/downloads.php?lang=fr/" hreflang="fr" href="https://linuxfr.org/redirect/108124">Téléchargements</a></li><li>lien nᵒ 11 : <a title="https://www.framboise314.fr/un-boitier-de-raspberry-pi-4-avec-freecad/" hreflang="fr" href="https://linuxfr.org/redirect/108135">Un tuto html de Framboise314</a></li></ul><div><h2 id="toc-sa-philosophie">Sa philosophie</h2>
<p>FreeCad est découpé en « Ateliers » qui sont chacun spécialisés dans un domaine : dessin paramétrique, génération de volumes 3D, éléments finis, architecture, etc.</p>
<p>FreeCAD vient avec un ensemble de ces ateliers, mais il en existe d’autres sous forme d’extensions faites par la communauté. Certaines finissent par intégrer la branche officielle. Un gestionnaire d’extensions permet de facilement les trouver et de les ajouter.</p>
<p>FreeCAD est en C++ avec une interface en Qt, mais Python joue un rôle important. Une console Python est intégrée pour interagir avec les éléments sans passer par l’interface graphique. Et beaucoup d’outils et d’ateliers sont aussi programmés en Python.</p>
<h2 id="toc-les-nouveautés">Les nouveautés</h2>
<ul>
<li>Sous le capot général, la grosse nouveauté est la création d’une classe interne « App::link » qui permet de rendre plus puissants la modélisation et l’assemblage des pièces. Son développeur a mis sur son <a href="https://github.com/realthunder/FreeCAD_assembly3/wiki/Link">git</a> une longue explication de son implémentation et une <a href="https://www.youtube.com/watch?v=yTDkJ7JZAWs">vidéo</a> expliquant les nouvelles possibilités.</li>
<li>Fin de migration vers Python 3 et Qt5.</li>
<li>Nombreux ajouts et améliorations dans tous les ateliers. Inutile de paraphraser, les <a href="https://wiki.freecadweb.org/Release_notes_0.19/fr">notes de version illustrées et en français</a> sont bien faites.</li>
</ul>
<h2 id="toc-un-coup-de-pouce-dautodesk">Un coup de pouce d’Autodesk</h2>
<p>Depuis ces derniers mois, il semble que la visibilité se soit grandement améliorée autour de FreeCAD. On trouve plus de tutos sur Youtube, et des streamers makers font des lives sur ce logiciel.<br>
Ce regain d’intérêt et d’exposition est semble-t-il à mettre au crédit d’Autodesk. En effet, celui-ci édite le logiciel Fusion 360 largement exploité dans la communauté makers. Elle avait pu jusqu’à récemment profiter d’une licence d’abonnement gratuite intéressante.</p>
<p>Mais en septembre 2020, Autodesk a <a href="https://www.autodesk.com/products/fusion-360/blog/changes-to-fusion-360-for-personal-use/">décidé unilatéralement de restreindre</a> les fonctionnalités comprises avec cette licence, et il faut désormais payer un abonnement pour les retrouver. Un <a href="//linuxfr.org/users/jona/journaux/la-licence-gratuite-de-fusion-360-d-autodesk-devient-plus-restrictive">journal</a> a été fait à l’époque sur linuxfr par Jona.</p>
<p>Les restrictions à partir de septembre 2020 (Document Autodesk) :<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f7777772e6175746f6465736b2e636f6d2f70726f64756374732f667573696f6e2d3336302f626c6f672f77702d636f6e74656e742f75706c6f6164732f323032302f30392f63686172742d322e706e67/chart-2.png" alt="Les changements décidés par Autodesk" title="Source : https://www.autodesk.com/products/fusion-360/blog/wp-content/uploads/2020/09/chart-2.png"></p>
<p>Sur cette problématique, vous pouvez avoir plus d’informations sur la vidéo de Barbatronic (en live et en français) intitulée <a href="https://www.youtube.com/watch?v=LzL17f_8OcE">Fusion 360 ou Fuyons 360</a>. <br>
Barbatronic est un maker français, gestionnaire d’un fablab et faisant pas mal de robotique. Il est (était ?) un grand utilisateur de Fusion 360.</p>
<p>Des makers se sont donc intéressés aux alternatives. FreeCAD était l’une d’elles, mais ne jouissait pas d’une très bonne réputation, principalement sur des problèmes de plantages et sur une interface vieillotte voire rebutante.<br>
Ce même Barbatronic s’est accroché malgré des débuts difficiles et au final, il a trouvé FreeCAD très intéressant. Il partage dans <a href="https://www.youtube.com/watch?v=cu9mK7seHT4">une autre vidéo son avis positif sur FreeCAD</a>. Il a trouvé que les fonctionnalités étaient bien là et que sur certains points même, FreeCAD était au-dessus de Fusion 360 (comme l’incorporation de vis ou la conception sur métal déplié) avec un effet “Wahou”.</p>
<h2 id="toc-un-alignement-des-planètes-favorables-à-freecad">Un alignement des planètes favorables à FreeCAD</h2>
<p>Il est possible que FreeCAD bénéficie donc de cette situation.<br>
FreeCAD est un logiciel ancien, avec une base solide. Il a évolué lentement mais surement. Un évènement extérieur le met finalement en lumière au bon moment, ses qualités et le bénéfice de sa licence sont en train finalement d’être reconnus.<br>
En conséquence, FreeCAD va sans doute gagner en notoriété et en réputation, ce qui ne sera que bénéfique sur le long terme.</p>
</div><div><a href="https://linuxfr.org/news/freecad-0-19.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/123719/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/freecad-0-19#comments">ouvrir dans le navigateur</a>
</p>
Axonepalm123Yves BourguignonBenoît SibaudPierre JarillonclaudexYsabeau 🧶 🧦https://linuxfr.org/nodes/123719/comments.atomtag:linuxfr.org,2005:News/402112020-12-26T13:25:27+01:002020-12-26T14:42:40+01:00Quelques logiciels libres pour sécuriser le travail collaboratif en ligne Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Cette dépêche est initialement basée sur le <a href="//linuxfr.org/users/mariepa/journaux/4-outils-open-source-pour-securiser-le-travail-collaboratif-en-ligne">journal de MariePa</a>, qui a été complété et enrichi pour lister des solutions libres pour le travail collaboratif sécurisé. Il y est donc question de clients libres, de serveurs libres, de chiffrement bout en bout, de confidentialité des données, etc.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://linuxfr.org/users/mariepa/journaux/4-outils-open-source-pour-securiser-le-travail-collaboratif-en-ligne" hreflang="fr" href="https://linuxfr.org/redirect/107536">Journal à l’origine de la dépêche</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-messagerie-instantan%C3%A9e---visio-conf%C3%A9rence">Messagerie instantanée / visio-conférence</a></li>
<li>
<a href="#toc-courriels">Courriels</a><ul>
<li><a href="#toc-sur-votre-ordinateur-voire-votre-serveur">Sur votre ordinateur voire votre serveur</a></li>
<li><a href="#toc-sur-smartphone">Sur smartphone</a></li>
<li><a href="#toc-services-tiers">Services tiers</a></li>
<li><a href="#toc-%C3%80-installer-soi-m%C3%AAme">À installer soi-même</a></li>
</ul>
</li>
<li><a href="#toc-partage-de-donn%C3%A9es-en-ligne">Partage de données en ligne</a></li>
<li><a href="#toc-%C3%89dition-collaborative">Édition collaborative</a></li>
</ul>
<h2 id="toc-messagerie-instantanée---visio-conférence">Messagerie instantanée / visio-conférence</h2>
<p>Certains de ces services sont recensés sur LinuxFr.org avec l’<a href="//linuxfr.org/tags/visioconf%C3%A9rence/public">étiquette visioconférence</a> lorsqu’elle est disponible. La plupart des outils de visio-conférence fournissent le service de messagerie instantanée, par contre de nombreux logiciels de messagerie instantanée n’offrent pas de fonctionnalité de visio-conférence.</p>
<ul>
<li>
<a href="https://element.io/"><strong>Element</strong></a> - précédemment <em>Riot.im</em> - pour la messagerie instantanée chiffrée ou non, en groupe ou en individuel, avec des passerelles vers les canaux IRC. Visio-conférence incluse.
<ul>
<li>Existe avec une interface web</li>
<li>Existe pour smartphone (dont <a href="https://f-droid.org/en/packages/im.vector.app">F-Droid</a>)</li>
<li>Utilisation du protocole de fédération Matrix, laissant l’utilisateur choisir le serveur auquel il veut se connecter (et il existe différents serveurs libres)</li>
</ul>
</li>
<li>
<a href="https://galene.org"><strong>Galène</strong> </a> code source sur <a href="https://github.com/jech/galene/">Github</a>
<ul>
<li>Nécessite peu de ressource au niveau du serveur.</li>
<li>Voir la <a href="//linuxfr.org/news/galene-un-serveur-de-videoconference-libre">récente dépêche dédiée</a>
</li>
<li>Serveur 100% libre (licence MIT), à utiliser avec un navigateur comme client</li>
</ul>
</li>
<li>
<a href="https://jami.net/"><strong>Jami</strong></a> : Messagerie instantanée, appels audio et vidéo, partage d’écran, conférences.
<ul>
<li>Complètement pair à pair, ne nécessite pas de serveurs pour fonctionner</li>
<li>Chiffré de bout en bout</li>
<li>100% libre (licence GPLv3, <a href="https://directory.fsf.org/wiki/GNU">paquet GNU</a>, <a href="https://directory.fsf.org/wiki/Collection:High_Priority_Projects">High Priority Project</a>)</li>
<li>Existe pour smartphone (dont <a href="https://f-droid.org/packages/cx.ring/">F-Droid</a>)</li>
<li>Existe pour <a href="https://play.google.com/store/apps/details?id=cx.ring">Android TV</a>
</li>
</ul>
</li>
<li>
<a href="https://jitsi.org/"><strong>Jitsi</strong></a>
<ul>
<li>Existe avec une interface web</li>
<li>Existe pour smartphone (dont <a href="https://f-droid.org/en/packages/org.jitsi.meet/">F-Droid</a>)</li>
<li>100% libre (APLv2), pour les clients et le serveur Videobridge</li>
</ul>
</li>
<li>
<a href="https://nextcloud.com/talk/"><strong>NextCloud Talk</strong></a> : Appels audio ou vidéo en tête-à-tête ou en groupe, conférences Web et messages instantanés
<ul>
<li>Communications entièrement chiffrées et transmises par votre propre serveur</li>
<li>100% libre (licence GPLv3)</li>
<li>Existe pour smartphone (dont <a href="https://f-droid.org/fr/packages/com.nextcloud.talk2/">F-Droid</a>)</li>
</ul>
</li>
<li>
<a href="https://f-droid.org/fr/packages/org.smssecure.smssecure/"><strong>Silence</strong></a> sur smartphone.
<ul>
<li>application de SMS chiffrés (nécessite que vos interlocuteurs disposent de Silence pour qu’ils restent chiffrés)</li>
<li>100% libre (licence GPLv3)</li>
</ul>
</li>
</ul>
<p>Element :<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f656c656d656e742e696f2f696d616765732f686f6d65706167652d616c6c2d706c6174666f726d732d315f312e706e67/homepage-all-platforms-1_1.png" alt="Messagerie Element" title="Source : https://element.io/images/homepage-all-platforms-1_1.png"></p>
<p>Galene :<br>
<img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313630383931363338382e706e67/1608916388.png" alt="Galene" title="Source : http://pix.toile-libre.org/upload/original/1608916388.png"></p>
<p>Jami :<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f6a616d692e6e65742f6173736574732f696d616765732f636f6e666572656e6365732e706e673f763d62626136653135396538/conferences.png?v=bba6e159e8" alt="Messagerie Jami" title="Source : https://jami.net/assets/images/conferences.png?v=bba6e159e8"></p>
<p>Jitsi :<br>
<img src="//img.linuxfr.org/img/687474703a2f2f7069782e746f696c652d6c696272652e6f72672f75706c6f61642f6f726967696e616c2f313630383931373333392e706e67/1608917339.png" alt="Jitsi" title="Source : http://pix.toile-libre.org/upload/original/1608917339.png"></p>
<p>Talk (Nextcloud) :<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f6e657874636c6f75642f7370726565642f6d61737465722f646f63732f63616c6c2d696e2d616374696f6e2e706e67/call-in-action.png" alt="Messagerie Talk" title="Source : https://raw.githubusercontent.com/nextcloud/spreed/master/docs/call-in-action.png"></p>
<p>Silence :<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f662d64726f69642e6f72672f7265706f2f6f72672e736d737365637572652e736d737365637572652f656e2d55532f70686f6e6553637265656e73686f74732f6c6973745f756e726561642e706e67/list_unread.png" alt="Messagerie Silence" title="Source : https://f-droid.org/repo/org.smssecure.smssecure/en-US/phoneScreenshots/list_unread.png"></p>
<h2 id="toc-courriels">Courriels</h2>
<h3 id="toc-sur-votre-ordinateur-voire-votre-serveur">Sur votre ordinateur voire votre serveur</h3>
<p>Par ordre alphabétique :</p>
<ul>
<li>
<a href="https://fr.wikipedia.org/wiki/GNOME_Evolution"><strong>evolution</strong></a> : le client de courriel historique de GNOME (GPL/LGPL)</li>
<li>
<a href="https://apps.nextcloud.com/apps/mail"><strong>Mail</strong></a> dans Nextcloud, particulièrement bien intégré, mais l'affichage des fils de discussion est difficile à suivre (AGPLv3).</li>
<li>
<a href="https://apps.nextcloud.com/apps/rainloop"><strong>Rainloop</strong></a> dans NextCloud. Mais, le carnet d’adresses n’est pas synchronisé avec l’application Contact (AGPLv3).</li>
<li>
<a href="https://www.thunderbird.net/fr/"><strong>Thunderbird</strong></a> : dans les dernières versions, le support d’<a href="https://fr.wikipedia.org/wiki/OpenPGP" title="Définition Wikipédia">OpenPGP</a> est inclus (le greffon Enigmail est devenu inutile). Licences MPL-1.1 / LGPL v2.1 / GPL v2 / MPL-2.0</li>
</ul>
<p>Rainloop sur NextCloud :<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f7069657272652d616c61696e2d622f7261696e6c6f6f702d6e657874636c6f75642f6d61737465722f73637265656e73686f74732f323031362e31302e32302d73637265656e73686f742e6a7067/2016.10.20-screenshot.jpg" alt="Rainloop sur NextCloud" title="Source : https://raw.githubusercontent.com/pierre-alain-b/rainloop-nextcloud/master/screenshots/2016.10.20-screenshot.jpg"></p>
<p>Mail sur NextCloud :<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f757365722d696d616765732e67697468756275736572636f6e74656e742e636f6d2f313337343137322f37393535343936362d32373865313630302d383039662d313165612d383265612d3761306437326132373034662e706e67/79554966-278e1600-809f-11ea-82ea-7a0d72a2704f.png" alt="NextCloud Mail" title="Source : https://user-images.githubusercontent.com/1374172/79554966-278e1600-809f-11ea-82ea-7a0d72a2704f.png"></p>
<p>Thunderbird :<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f7777772e7468756e646572626972642e6e65742f6d656469612f696d672f6c31306e2f66722f7468756e646572626972642f6c616e64696e672f73637265656e73686f742d6c696e75782e706e67/screenshot-linux.png" alt="Thunderbird" title="Source : https://www.thunderbird.net/media/img/l10n/fr/thunderbird/landing/screenshot-linux.png"></p>
<p>GNOME Evolution (avec une surcouche de couleur pour <a href="https://help.gnome.org/users/evolution/stable/intro-main-window.html.fr">illustrer les différentes zones dans la documentation</a>) :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f68656c702e676e6f6d652e6f72672f75736572732f65766f6c7574696f6e2f737461626c652f666967757265732f77696e646f772d6f766572766965772d6c61796572732e706e67/window-overview-layers.png" alt="GNOME Evolution" title="Source : https://help.gnome.org/users/evolution/stable/figures/window-overview-layers.png"></p>
<h3 id="toc-sur-smartphone">Sur smartphone</h3>
<p>Quelques clients sont disponibles directement sur le dépôt de logiciels libres <a href="https://f-droid.org">F-Droid</a> :</p>
<ul>
<li>
<a href="https://f-droid.org/en/packages/eu.faircode.email/">FairEmail</a> : application de messagerie électronique entièrement fonctionnelle, open source et axée sur la confidentialité (certaines fonctionnalités nécessitent un <a href="https://email.faircode.eu/donate/">don</a> unique d’au minimum un euro). Licence GPL v3</li>
<li>
<a href="https://f-droid.org/fr/packages/com.fsck.k9/">K-9 Mail</a> : un des seuls clients de courriel pour smartphone qui permet de rédiger des messages en texte seul. Multi-compte. Très bien intégré. Licence APL v2</li>
<li>
<a href="https://f-droid.org/en/packages/org.dystopia.email/">SimpleEmail</a> : logiciel libre de messagerie minimaliste et respectueuse de la vie privée. Licence GPL v3</li>
</ul>
<p>Captures d’écran :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f64726f69642e746f6f6c732f77702d636f6e74656e742f75706c6f6164732f323032302f30362f6b392d6d61696c2e6a7067/k9-mail.jpg" alt="K9mail" title="Source : https://droid.tools/wp-content/uploads/2020/06/k9-mail.jpg"><br>
<img src="//img.linuxfr.org/img/68747470733a2f2f662d64726f69642e6f72672f7265706f2f65752e66616972636f64652e656d61696c2f656e2d55532f70686f6e6553637265656e73686f74732f385f636f6d706f73652e706e67/8_compose.png" alt="FailEmail" title="Source : https://f-droid.org/repo/eu.faircode.email/en-US/phoneScreenshots/8_compose.png"><br>
<img src="//img.linuxfr.org/img/68747470733a2f2f662d64726f69642e6f72672f7265706f2f6f72672e647973746f7069612e656d61696c2f656e2d55532f70686f6e6553637265656e73686f74732f31315f73637265656e73686f742e706e67/11_screenshot.png" alt="SimpleEmail" title="Source : https://f-droid.org/repo/org.dystopia.email/en-US/phoneScreenshots/11_screenshot.png"></p>
<h3 id="toc-services-tiers">Services tiers</h3>
<p>Par ordre alphabétique :</p>
<ul>
<li>
<a href="https://chatons.org/fr/find-by-services"><strong>Les CHATONS</strong></a> : un bel annuaire, pour ce collectif rassemblant des acteurs proposant des services en ligne libres, éthiques, décentralisés et solidaires</li>
<li>
<a href="https://www.lautre.net"><strong>Lautre.net</strong></a> : web et courriels, association prônant le logiciel libre, la neutralité du net et l’autonomie des membres.</li>
</ul>
<h3 id="toc-À-installer-soi-même">À installer soi-même</h3>
<p>Par ordre alphabétique, non seulement le client est disponible, mais aussi le volet serveur permettant l’auto-hébergement :</p>
<ul>
<li>
<a href="https://alternc.com"><strong>AlternC</strong></a> : <a href="https://www.lautre.net">Lautre.net</a> et <a href="https://www.marsnet.org/">Mars.net</a> se basent sur AlternC pour leur fonctionnement</li>
<li><a href="https://freedombox.org/"><strong>FreedomBox</strong></a></li>
<li>
<a href="https://labriqueinter.net/"><strong>La Brique Internet</strong></a> pour son propre hébergement sur un routeur, avec utilisation de Yunohost.</li>
<li>
<a href="https://yunohost.org/"><strong>Yunohost</strong></a> (AGPL)</li>
</ul>
<h2 id="toc-partage-de-données-en-ligne">Partage de données en ligne</h2>
<p><a href="https://fr.wikipedia.org/wiki/Nextcloud"><strong>NextCloud</strong></a> (AGPLv3) : pour échanger, chiffrer des documents, gérer ses rendez-vous, ses calendriers, ses contacts. Synchronisation avec le smartphone et d’autres ordinateurs. Possibilité de chiffrer les documents des utilisateurs rendant les fichiers illisibles en cas de compromission du serveur.<br>
<a href="https://owncloud.com"><strong>ownCloud</strong></a> (AGPLv3) : similaire à NextCloud, dont il est l'origine en 2016.<br>
<a href="https://www.seafile.com"><strong>SeaFile</strong></a> (AGPLv3 pour le serveur, APLv2 ou GPLv2 ou 3 suivant les composants) : partages et chiffrements des fichiers.</p>
<p>Ces applications permettent plusieurs niveaux et mécanismes de chiffrement : </p>
<ul>
<li>le chiffrement en transit via le protocole HTTPS et les protocoles TLS les plus récents, </li>
<li>le chiffrement au repos qui s’effectue à l’aide d’une clé maître placée dans le stockage ou dans un module matériel de sécurité,</li>
<li>le chiffrement de bout-en-bout ajoute une couche de sécurité supplémentaire (si vous avez confiance dans le serveur) qui assure que les fichiers ne sont accessibles qu’aux utilisateurs prévus.</li>
</ul>
<p>L’application de chiffrement de bout-en-bout est librement disponible et activable pour NextCloud et Seafile.</p>
<h2 id="toc-Édition-collaborative">Édition collaborative</h2>
<ul>
<li>
<a href="https://www.libreoffice.org/download/libreoffice-online/">LibreOffice Online</a> (MPLv2) : serveur pour afficher et éditer collaborativement des documents variés dans un navigateur web</li>
<li>
<a href="https://www.onlyoffice.com/fr/collaboration-platform.aspx">OnlyOffice Groups</a> (APLv2) : serveur pour la collaboration gérant les documents, les projets, le CRM et les courriels</li>
<li>
<a href="https://cryptpad.fr/">CryptPad</a> (AGPLv3) : logiciel libre de collaboration chiffrée avec « équipes », partage de fichiers, et discussion chiffrée avec un groupe d’utilisateurs. Voir par exemple <a href="//linuxfr.org/news/cryptpad-version-3-3-nouvelle-fonctionnalite-teams">la dépêche sur la version 3.3 (la dernière disponible étant la 3.25)</a>
</li>
</ul>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f7777772e6c696272656f66666963652e6f72672f6173736574732f55706c6f6164732f4c696272654f66666963652d4f6e6c696e652d5772697465722e706e67/LibreOffice-Online-Writer.png" alt="Libroffice Online" title="Source : https://www.libreoffice.org/assets/Uploads/LibreOffice-Online-Writer.png"><br>
<img src="//img.linuxfr.org/img/68747470733a2f2f63727970747061642e66722f637573746f6d697a652f696d616765732f7061645f73637265656e73686f742e706e673f7665723d332e32352e302d36/pad_screenshot.png?ver=3.25.0-6" alt="CryptPad" title="Source : https://cryptpad.fr/customize/images/pad_screenshot.png?ver=3.25.0-6"><br>
<img src="//img.linuxfr.org/img/68747470733a2f2f7374617469632d7777772e6f6e6c796f66666963652e636f6d2f696d616765732f776f726b706c6163652f6361726f7573656c2f66722f736c6964655f646f63756d656e74732e706e67/slide_documents.png" alt="OnlyOffice Groups" title="Source : https://static-www.onlyoffice.com/images/workplace/carousel/fr/slide_documents.png"></p>
</div><div><a href="https://linuxfr.org/news/quelques-logiciels-libres-pour-securiser-le-travail-collaboratif-en-ligne.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/122610/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/quelques-logiciels-libres-pour-securiser-le-travail-collaboratif-en-ligne#comments">ouvrir dans le navigateur</a>
</p>
Benoît SibaudGGBAudEricolivierweborfenorYsabeau 🧶 🧦thomasvMariePagouttegdNils Ratusznikhttps://linuxfr.org/nodes/122610/comments.atomtag:linuxfr.org,2005:News/401552020-12-08T11:34:12+01:002020-12-30T10:48:32+01:00Pharo : quoi de neuf ?Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Au début de Pharo, il a eu droit à une série de dépêches sur <em>LinuxFr.org</em>. Mais depuis 2016 et la <a href="//linuxfr.org/news/sortie-du-langage-pharo-et-de-son-environnement-de-developpement-en-version-5-0">dépêche sur Pharo 5.0</a>, il faut bien reconnaître qu’il n’y a pas vraiment eu de nouvelles concernant Pharo sur <em>LinuxFr.org</em>. Ayant acheté le numéro de novembre‐décembre de <em><a href="https://www.programmez.com/magazine/programmez-243-pdf">Programmez</a></em>, j’y ai lu un dossier intéressant sur Smalltalk qui présente Pharo comme une implémentation moderne et libre de Smalltalk. Cela m’a motivé à me renseigner un peu sur Pharo et, finalement, je me suis dit que certains seraient peut‑être intéressés de voir ce qu’est devenu Pharo depuis la dernière dépêche de 2016.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://pharo.org/" hreflang="en" href="https://linuxfr.org/redirect/107387">Site officiel</a></li><li>lien nᵒ 2 : <a title="https://github.com/pharo-project/pharo" hreflang="en" href="https://linuxfr.org/redirect/107388">Pharo sur GitHub</a></li><li>lien nᵒ 3 : <a title="https://www.fun-mooc.fr/courses/course-v1:inria+41024+session01/about" hreflang="fr" href="https://linuxfr.org/redirect/107389">Le MOOC Pharo</a></li><li>lien nᵒ 4 : <a title="https://www.youtube.com/channel/UCp3mNigANqkesFzdm058bvw" hreflang="en" href="https://linuxfr.org/redirect/107415">Pharo sur YouTube</a></li><li>lien nᵒ 5 : <a title="https://pharoweekly.wordpress.com/" hreflang="en" href="https://linuxfr.org/redirect/107416">Everything you wanted to know about http://www.pharo.org without being forced to read 1000 mails</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-pharo-cest-quoi">Pharo, c’est quoi</a><ul>
<li><a href="#toc-la-pub">La pub</a></li>
<li><a href="#toc-un-peu-dhistoire">Un peu d’histoire</a></li>
<li><a href="#toc-pharo--smalltalk">Pharo = Smalltalk ?</a></li>
<li><a href="#toc-la-syntaxe-de-pharo">La syntaxe de Pharo</a></li>
<li>
<a href="#toc-projets-utilisant-pharo">Projets utilisant Pharo</a><ul>
<li><a href="#toc-drgeo">Dr. Geo</a></li>
<li><a href="#toc-honey-ginger">Honey Ginger</a></li>
<li><a href="#toc-monde-bancaire">Monde bancaire</a></li>
<li><a href="#toc-phratch">Phratch</a></li>
</ul>
</li>
</ul>
</li>
<li>
<a href="#toc-quoi-de-neuf">Quoi de neuf ?</a><ul>
<li><a href="#toc-iceberg">Iceberg</a></li>
<li><a href="#toc-bootstrapping">bootstrapping</a></li>
<li><a href="#toc-calypso">Calypso</a></li>
<li><a href="#toc-gestion-de-diff%C3%A9rents-%C3%A9tats-pour-lestraits">Gestion de différents états pour les traits</a></li>
<li><a href="#toc-64bits">64 bits</a></li>
</ul>
</li>
<li><a href="#toc-et-pour-la-suite">Et pour la suite ?</a></li>
<li>
<a href="#toc-ressources">Ressources</a><ul>
<li>
<ul>
<li><a href="#toc-mooc">MOOC</a></li>
<li><a href="#toc-des-livres">Des livres</a></li>
<li><a href="#toc-mais-aussi">Mais aussi</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="toc-pharo-cest-quoi">Pharo, c’est quoi</h2>
<h3 id="toc-la-pub">La pub</h3>
<p>Selon la <a href="https://pharo.org/">page d’accueil</a> de son site Internet (traduction par mes soins) :</p>
<blockquote>
<p>Pharo est un langage de programmation orientée objet pur et un environnement puissant, axé sur la simplicité et le retour d’information immédiat (pensez à un EDI et un système d’exploitation réunis en une même suite logicielle).</p>
</blockquote>
<p>Sur la même page, on vous promet :</p>
<ul>
<li>
<strong>un langage simple et puissant :</strong> pas de constructeurs, pas de déclaration de types, pas d’interfaces, pas de types primitifs ; pourtant, un langage puissant et élégant avec une syntaxe complète qui tient sur une carte postale ! Pharo, ce sont des objets et des messages ; et c’est tout ;</li>
<li>
<strong>un environnement vivant et immersif :</strong> un retour d’information immédiat à tout moment de votre développement — développement, test, débogage — ; même dans les environnements de production, vous ne serez plus jamais bloqué dans la compilation et le déploiement des étapes !</li>
<li>
<strong>une expérience de débogage étonnante :</strong> l’environnement Pharo comprend un débogueur comme vous n’en avez jamais vu auparavant. Il vous permet de parcourir le code, de relancer l’exécution des méthodes, de créer des méthodes à la volée, et bien plus encore !</li>
<li>
<strong>Pharo est à vous :</strong> Pharo est le fruit d’une incroyable communauté, avec plus de cent contributeurs pour la dernière révision de la plate‑forme et des centaines de personnes qui contribuent constamment à des cadriciels et des bibliothèques ;</li>
<li>
<strong>entièrement open source :</strong> la pile complète de Pharo est publiée sous licence MIT.</li>
</ul>
<h3 id="toc-un-peu-dhistoire">Un peu d’histoire</h3>
<p>La généalogie de Pharo remonte à Smalltalk et particulièrement la version Smalltalk-72 qui a été créée par Alan Kay pour relever le défi de spécifier un langage de programmation puissant en moins d’une page. Les fondamentaux sont là, mais la maturité viendra vraiment avec la version Smalltalk-80.</p>
<p>Ces développements se déroulent dans le cadre du Xerox <a href="https://fr.wikipedia.org/wiki/Palo_Alto_Research_Center" title="Palo Alto Research Center">PARC</a> qui verra dans les années 70 passer une concentration hallucinante de génies de l’informatique qui poseront les bases de nombreux éléments de l’informatique moderne. Xerox s’est montré capable de monter un centre de recherche particulièrement fécond mais sera moins inspiré concernant la valorisation de ce travail.</p>
<p>Nous éludons une partie de l’histoire pour nous retrouver en 1995 chez Apple où Alan Kay et Dan Ingalls (un autre ancien du PARC de Xerox) créent <a href="https://fr.wikipedia.org/wiki/Squeak">Squeak</a>, qui est une nouvelle implémentation de Smalltalk hautement portable et dont la machine virtuelle est elle‑même écrite en Smalltalk. Le focus de Squeak est assez orienté sur les applications ludiques et multimédia.</p>
<p>Squeak n’est pas la seule suite qui a été donnée à Smalltalk, on peut par exemple citer :</p>
<ul>
<li>
<a href="https://fr.wikipedia.org/wiki/GNU_Smalltalk">GNU Smalltalk</a> ;</li>
<li>
<a href="https://fr.wikipedia.org/wiki/VisualWorks">VisualWorks</a>.</li>
</ul>
<p>Pharo, quant à lui, est un divergence de Squeak qui était justifiée de la manière suivante dans <a href="//linuxfr.org/news/naissance-dun-projet-libre-pharo">la dépêche annonçant le lancement de Pharo</a> :</p>
<blockquote>
<p>Le projet Pharo est un projet de Smalltalk open source. L’objectif de Pharo est de pousser Squeak au niveau supérieur.<br>
Pharo est en effet un <em>fork</em> de Squeak. Les <em>forks</em> apparaissent pour résoudre des problèmes difficiles à résoudre dans le projet père. En effet, Squeak est otage de plusieurs sous‑communautés (très amicales au demeurant), ce qui ne conduit à aucune évolution utile pour ces sous‑communautés, et cela résulte finalement en une myriade de <em>forks</em> (Etoys, OpenCroquet, Sophie, Squeak, Qwaq…), chacun spécifique à une communauté/projet, mais insuffisamment généraliste pour une portée plus large.</p>
</blockquote>
<p>Dans un <a href="//linuxfr.org/nodes/85533/comments/1223167">commentaire</a> de l’annonce de la version 1.2, ceci était exprimé de la manière suivante :</p>
<blockquote>
<p>Pharo est un <em>fork</em> de Squeak, suite à une divergence d’intérêts entre des développeurs qui voulaient continuer à développer une plate‑forme multimédia un peu fourre‑tout (Squeak) et ceux qui voulaient que se développe un Smalltalk avec de meilleures pratiques logicielles (tests unitaires, découplage des modèles et de l’interface utilisateur, intégration continue des modifications…). Pharo vise une cible plus professionnelle avec notamment le développement Web avec Seaside.</p>
</blockquote>
<h3 id="toc-pharo--smalltalk">Pharo = Smalltalk ?</h3>
<p>Alors qu’au départ, Pharo a souvent été présenté comme une implémentation de Smalltalk, la réalité est un peu plus complexe que cela. Pharo est désormais plus présenté comme un langage (fortement) inspiré par Smalltalk <a href="//linuxfr.org/nodes/102093/comments/1536800">comme l’exprimait Damien Cassou</a> dans les commentaires de la <a href="//linuxfr.org/news/sortie-du-langage-pharo-et-de-son-environnement-de-developpement-en-version-3-0">dépêche annonçant Pharo 3.0</a> :</p>
<blockquote>
<p>Effectivement, on peut voir Pharo comme une implémentation du standard Smalltalk avec son environnement de développement et ses bibliothèques de code. Cependant, nous avons décidé d’arrêter de dire ça car le standard Smalltalk n’évolue plus alors qu’on améliore Pharo en permanence.<br>
Ça signifie qu’on s’autorise à ne pas être compatible avec le standard. Par exemple, Pharo possède la notion de <em>trait</em> (un mécanisme d’héritage multiple de méthodes) alors que le standard ne le prévoit pas. De même, dans la prochaine version de Pharo, on n’utilisera plus de chaînes de caractères pour déclarer les variables d’instances car on utilisera à la place les <em>Slots</em> (un mécanisme beaucoup plus poussé qui permet d’avoir différents types de variables d’instances avec des comportements différents). Là encore, on s’éloigne du standard Smalltalk. Enfin, le standard définit une API qu’on s’autorise à remplacer petit à petit par une autre plus moderne.<br>
Pour toutes ces raisons, on parle maintenant de « <em>Smalltalk-inspired</em> » et plus d’implémentation de Smalltalk.</p>
</blockquote>
<h3 id="toc-la-syntaxe-de-pharo">La syntaxe de Pharo</h3>
<p>La syntaxe de Pharo est très proche de celle de Smalltalk et continue à tenir sur une carte postale. Quand on dit que la syntaxe de Pharo tient sur une carte postale, il faut l’entendre comme le fait que l’on peut illustrer toutes les caractéristiques de cette syntaxe sur un code qui tient sur une carte postale.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f7468756d622f612f61372f506861726f5f73796e7461785f706f7374636172642e7376672f38303070782d506861726f5f73796e7461785f706f7374636172642e7376672e706e67/800px-Pharo_syntax_postcard.svg.png" alt="La syntaxe de Pharo sur une carte postale, par Xkriva11 — Own work, CC BY‑SA 4.0, https://commons.wikimedia.org/w/index.php?curid=69598356" title="Source : https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Pharo_syntax_postcard.svg/800px-Pharo_syntax_postcard.svg.png"></p>
<p>Pour avoir une explication un tout petit peu plus détaillée, on pourra se référer à cette <em><a href="http://files.pharo.org/media/pharoCheatSheet.pdf">Cheat Sheet</a></em> qui tient sur deux pages.</p>
<p>Une des caractéristiques les plus marquantes de Pharo est son caractère très pur de langage orienté objet. Pratiquement tout est objet. Entre autres, il y a très peu de mots réservés. Notamment, on retrouve très peu d’instructions de contrôle intégrées au langage telles que <code>if … then … (else …)</code>. Ce genre de contrôle sera implémenté en utilisant des objets et en leur adressant des messages. Dans le cas d’un <code>if … then …</code>, on aura :</p>
<pre><code class="C"><span class="n">Condition</span>
<span class="nl">ifTrue</span><span class="p">:</span> <span class="n">quelque</span> <span class="n">chose</span>
<span class="nl">ifFalse</span><span class="p">:</span> <span class="n">autre</span> <span class="n">chose</span></code></pre>
<p><code>ifTrue:ifFalse:</code> est une méthode de la classe abstraite Boolean avec une implémentation différente dans les sous-classes concrètes True et False. Voici l'implémentation de la méthode dans la classe True:</p>
<pre><code>True>>ifTrue: trueBlock ifFalse: falseBlock
^ trueBlock value
</code></pre>
<p>La première ligne est la signature de la méthode <code>ifTrue:ifFalse:</code> qui prend 2 arguments (<code>trueBlock</code> et <code>falseBlock</code>). La deuxième ligne est l'implémentation qui retourne la valeur du premier argument. Dans la classe <code>False</code>, l'implémentation est la même sauf que c'est la valeur du deuxième argument qui est retournée. Grâce à ça, le langage n'a pas besoin de <code>if/then/else</code>.</p>
<p>Dans le code de l'article, un seul message <code>ifTrue:ifFalse:</code> est envoyé à l'objet <code>Condition</code> pour que la méthode du même nom soit exécutée.</p>
<h3 id="toc-projets-utilisant-pharo">Projets utilisant Pharo</h3>
<p>Un langage avec des caractéristiques plaisantes, c’est sympa. Mais c’est encore plus intéressant quand l’on peut voir ce que cela donne dans des cas concrets. Ici, une sélection très loin d’être exhaustive de projets réalisés avec Pharo. Les plus curieux trouveront <a href="https://pharo.org/success">une liste plus longue sur le site de Pharo</a>. Liste qui malheureusement n’est pas totalement à jour dans la mesure où elle renseigne aussi des projets qui ne semblent plus être actifs aujourd’hui.</p>
<h4 id="toc-drgeo">Dr. Geo</h4>
<p>Sans doute le projet le plus connu des moules de <em>LinuxFr.org</em>, vu que ce projet y a eu droit à <a href="//linuxfr.org/tags/dr_geo/public">une série de dépêches</a>.</p>
<p><img src="https://img.linuxfr.org/img/687474703a2f2f666f72756d2e647267656f2e65752f66696c652f6e343032343930332f44757265722e706e67/Durer.png" alt="Illustration Dr. Geo"></p>
<p><a href="https://fr.wikipedia.org/wiki/DrGeo">Dr. Geo</a> est un logiciel de géométrie interactive euclidienne du plan, pour une utilisation à l’école secondaire et primaire. Il permet d’organiser des activités pédagogiques dans l’enseignement de la géométrie, voire d’autres domaines liés des mathématiques. Il propose également une approche de la géométrie dynamique par la programmation, soit par l’utilisation de script(s) intégré(s) à une figure, soit par une description purement programmatique d’une construction géométrique.</p>
<h4 id="toc-honey-ginger">Honey Ginger</h4>
<p>Honey Ginger est un simulateur hydrodynamique à particules lisses (<em>smoothed‐particle hydrodynamics simulator</em>) avec une visualisation et une interactivité riches. Je ne sais pas à quoi cela peut réellement servir et comment on peut l’intégrer à d’autres choses pour arriver à quelque chose d’utile, mais je trouve les effets dans la <a href="http://">vidéo de démo</a> assez amusants.</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f66696c65732e706861726f2e6f72672f737563636573732d73746f726965732f696d616765732f486f6e657947696e6765722d4c61676f6f6e52656672616374696f6e576f726c642e706e67/HoneyGinger-LagoonRefractionWorld.png" alt="Illustration de Honey Ginger" title="Source : http://files.pharo.org/success-stories/images/HoneyGinger-LagoonRefractionWorld.png"></p>
<h4 id="toc-monde-bancaire">Monde bancaire</h4>
<p>Je trouve intéressant d’observer que Pharo est aussi utilisé dans des mondes « sérieux » et pas seulement chez des <em>geeks</em>, des académiques ou des graphistes. Ainsi, Pharo a été utilisé dans des <a href="https://fr.wikipedia.org/wiki/Guichet_automatique_bancaire#Les_GAB_aujourd'hui_en_France" title="guichet automatique bancaire">GAB</a> déployés dans les rues de Moscou de 2008 à 2015. Cet usage ayant fini semble‐t‐il suite à la fermeture de la banque pour d’autres raisons (information que je n’ai pas vérifiée). On retrouve des vidéos de ces GAB par <a href="https://astares.blogspot.com/2017/10/pharo-success-story-driving-atms-in.html">ici</a>. Autre banque à avoir eu recours à Pharo : CSOB. CSOB est une des trois plus grandes banques en République tchèque et est une filiale à 100 % de la banque belge KBC. Sinon, en Argentine, <a href="https://mercapabbaco.com/?idioma=en">des banques utilisent Pharo pour la gestion des stocks</a>.</p>
<h4 id="toc-phratch">Phratch</h4>
<p>Bien que je craigne que le projet soit abandonné depuis lors, j’ai envie de mentionner ce projet de réimplémentation de <a href="https://fr.wikipedia.org/wiki/Scratch_(langage)">Scratch</a> en <a href="https://vimeo.com/73307234">Pharo</a>. Premièrement, parce que je suis un peu inquiet de l’évolution de Scratch qui me semble avec la version 3 aller vers une dépendance à Internet, alors que les versions précédentes pouvaient fonctionner sur un ordinateur en totale autonomie. Deuxièmement, parce que c’est quelque peu particulier de voir un projet être implémenté en Pharo alors que le projet initial était écrit en Smalltalk (les versions suivantes sont passées en ActionScript puis en JavaScript). Comme un retour aux sources. Pour ceux qui aiment ce genre de programme et qui sont prêts à se passer dans le nom de la référence explicite à Scratch, il y a aussi <a href="https://github.com/EiichiroIto/NovaStelo">NovaStelo</a> qui revisite le genre.</p>
<h2 id="toc-quoi-de-neuf">Quoi de neuf ?</h2>
<h3 id="toc-iceberg">Iceberg</h3>
<p>Le gestionnaire de versions historique de Pharo était Monticello. Depuis Pharo 6.0, il a été remplacé par Iceberg qui fait le pont avec Git.</p>
<p>L’esprit de Monticello était très similaire à Git en tant que système de gestion de versions distribué. Le système Monticello était taillé sur mesure pour les systèmes Smalltalk. Une bibliothèque cliente et un serveur ou forge Monticello sont nécessaires pour pouvoir l’utiliser.</p>
<p>Les raisons qui ont poussé Pharo à aller au‑delà de Monticello pour favoriser une compatibilité avec Git sont :</p>
<ol>
<li>le recours à Git permet à la communauté Pharo d’employer des forges standard déjà maintenues permettant de ne pas devoir y consacrer des ressources qui peuvent être plus utilement employées ailleurs dans le cadre de Pharo ;</li>
<li>Git est devenu <em>de facto</em> le standard comme gestionnaire de versions, imposer d’en utiliser un autre tel que Monticello crée une barrière à l’entrée supplémentaire dans la découverte de Pharo ; faire usage de Git permet ainsi de réduire le coût d’entrée pour un informaticien découvrant Pharo ;</li>
<li>certaines forges telles que GitHub ou GitLab offrent une belle visibilité pour les projets qu’elles hébergent ; ainsi, l’intégration avec Git permet aussi aux développeurs en Pharo de bénéficier de cette visibilité.</li>
</ol>
<p>Iceberg permet naturellement les opérations classiques d’un dépôt Git (création, clonage, gestion des branches, <em>commits</em>, fusion de branches…). La particularité d’Iceberg c’est qu’il essaie d’articuler le monde des fichiers source (partie Git) avec la représentation objet omniprésente dans le monde Smalltalk. Par exemple, les algorithmes de diff ne mettent pas seulement en lumière une à une les lignes de codes impactées (approche Git) mais mettront en évidence les méthodes modifiées ainsi que leurs classes.</p>
<h3 id="toc-bootstrapping">bootstrapping</h3>
<p>Depuis Pharo 7.0, Pharo peut être « bootstrappé » à partir d’une version antérieure de Pharo. Dans ce contexte, « <a href="https://fr.wikipedia.org/wiki/Bootstrap_(compilateur)">boostrapper</a> » signifie compiler un langage en utilisant un compilateur écrit dans ce même langage. En d’autres mots, l’image d’une nouvelle version de Pharo est générée par une application Pharo qui s’exécute dans une version antérieure de Pharo.</p>
<p>Au‑delà de l’élégance de la démarche de créer la version suivante à partir de la précédente, cela permet aussi de créer des noyaux de Pharo réduits. Cela peut être intéressant soit dans une perspective de sécurité (on réduit la surface d’attaque), soit dans une perspective d’informatique embarquée (on réduit l’usage de la mémoire pour Pharo). Il est ainsi possible d’obtenir à l’aide de ce <em>bootstrap</em> des noyaux de l’ordre de 200 Kio. </p>
<h3 id="toc-calypso">Calypso</h3>
<p>Calypso est depuis Pharo 7.0 le nouveau navigateur système de Pharo (<em>system browser</em>). Il remplace Nautilus, apporte des améliorations pour le travail à distance et des capacités de navigation plus avancées.</p>
<h3 id="toc-gestion-de-différents-états-pour-lestraits">Gestion de différents états pour les traits</h3>
<p>Avec Pharo comme pour Smalltalk, l’héritage est simple. Une classe n’hérite que d’une seule autre. En revanche, pour quand même pouvoir gérer une forme d’héritage multiple, Pharo implémente la notion de <a href="https://fr.wikipedia.org/wiki/Trait_(programmation)">traits</a>. Cela permet de partager des comportements entre classes au‑delà de la hiérarchie des classes. Depuis Pharo 7.0, les <em><a href="https://www.slideshare.net/esug/stateful-traits-in-pharo">stateful traits</a></em> sont implémentés.</p>
<h3 id="toc-64bits">64 bits</h3>
<p>Dans ses premières versions, Pharo était une version 32 bits. Depuis Pharo 6.0, une version 64 bits est disponible pour GNU/Linux et macOS. C’est également le cas pour Windows depuis Pharo 8.0.</p>
<h2 id="toc-et-pour-la-suite">Et pour la suite ?</h2>
<p>La publication de Pharo 9.0 est prévue pour mars 2021. Dans les cartons, une série de nouvelles fonctionnalités :</p>
<ul>
<li>nouvelle syntaxe de classe ;</li>
<li>commentaire en Microdown (une sorte de Markdown) ;</li>
<li>des nouveaux outils ;</li>
<li>nouveau cadriciel graphique (avec possibilités de rendu GTK) ;</li>
<li>nouveau mécanisme de complétion automatique ;</li>
<li>optimisation du compilateur ;</li>
<li>nouvelles machines virtuelles sur ARM 64 bits ;</li>
<li>nouveau FFI ;</li>
<li>ses tonnes de corrections et tests…</li>
</ul>
<h2 id="toc-ressources">Ressources</h2>
<p>Pharo n’est clairement pas un projet qui se cache ou qui est difficile d’accès sur Internet. Il y a pas mal de ressources qui permettent de le découvrir même pour de parfaits <em>newbies</em>. On retrouvera pas mal d’<a href="https://pharo.org/documentation">informations sur le site officiel de Pharo</a>.</p>
<h4 id="toc-mooc">MOOC</h4>
<p>Mettons en exergue la possibilité de suivre un <a href="https://www.fun-mooc.fr/courses/course-v1:inria+41024+session01/about">MOOC</a> sur <em><a href="http://www.fun-mooc.fr">www.fun-mooc.fr</a></em>. C’est la première version du MOOC remis à jour, mais il se base sur le MOOC précédent qui a connu au minimum quatre sessions. Plutôt orienté pour des personnes ayant une expérience de la programmation, il devrait cependant être accessible à toute personne motivée. Pour contenter tout le monde, le MOOC est constitué de cinq parcours :</p>
<ul>
<li>débutant complet ;</li>
<li>en quête de Web ;</li>
<li>rafraîchir votre vision OO ;</li>
<li>devenir un expert Pharo ;</li>
<li>en quête de magie noire.</li>
</ul>
<p>Ces profils sont complémentaires : pour devenir un expert en Pharo, il faut commencer par le profil débutant et compléter avec les autres parcours.</p>
<h4 id="toc-des-livres">Des livres</h4>
<p>Une série de <a href="http://books.pharo.org/">livres sont disponibles sur Internet</a>. Tous sont téléchargeables gratuitement. Une partie de ceux‑ci sont disponibles en version imprimée sur <em><a href="https://www.lulu.com/search?adult_audience_rating=00&keyword=object-oriented+programming&page=1&pageSize=10&q=Pharo">www.lulu.com</a></em>. Je ne les ai pas toutes vérifiées mais il semble que tous les livres sont sous des licences CC. Parfois libre, parfois NC.</p>
<p>Ce ne sont pas vraiment des livres, mais pour ceux qui veulent aller plus loin, il y a aussi une <a href="https://hal.inria.fr/PHARO/browse/meta-title">recension des articles académiques</a> liés à Pharo.</p>
<h4 id="toc-mais-aussi">Mais aussi</h4>
<p>Sur la <a href="https://www.youtube.com/channel/UCp3mNigANqkesFzdm058bvw">chaîne YouTube de Pharo</a>, on retrouve une grande variété de vidéos, notamment des archives des Pharo Tech Talks qui sont organisées tous les mois. On retrouve aussi de nombreuses <a href="https://www.slideshare.net/pharoproject/presentations">présentations</a> réalisées sur Pharo ou certaines de ses applications. Si vous désirez discuter avec la communauté Pharo sur Discord, c’est <a href="https://discord.com/invite/QewZMZa">par là</a>. Si vous voulez obtenir des nouvelles régulières, <a href="https://pharoweekly.wordpress.com">ce blog est à conseiller</a>.</p>
</div><div><a href="https://linuxfr.org/news/pharo-quoi-de-neuf.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/122312/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/pharo-quoi-de-neuf#comments">ouvrir dans le navigateur</a>
</p>
tisaacDavy Defaudstepharoclaudexbobble bubblepalm123Benoît SibaudBAudorfenorhttps://linuxfr.org/nodes/122312/comments.atomtag:linuxfr.org,2005:News/401162020-11-07T23:36:18+01:002020-11-07T23:36:18+01:00Migration de Jira à Tuleap : nouvelle fonctionnalitéLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Cela faisait un moment que la communauté le demandait, alors l’équipe R & D de Tuleap l’a fait : une importation simple et rapide des <em>issues</em> (tickets, artefacts) de Jira vers Tuleap.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f7777772e74756c6561702e6f72672f77702d636f6e74656e742f75706c6f6164732f323032302f31312f696d706f72742d6973737565732d6a6972612d746f2d54756c6561702d65313630343637323838323534362e706e67/import-issues-jira-to-Tuleap-e1604672882546.png" alt="Importer vos données de Jira vers Tuleap" title="Source : https://www.tuleap.org/wp-content/uploads/2020/11/import-issues-jira-to-Tuleap-e1604672882546.png"></p>
<p>Petit rappel au cas où : Jira et Tuleap sont des solutions de gestion de projet et de suivi des artefacts. En complément d’<a href="https://www.tuleap.org/fr/produit/fonctionnalites/gestion-projet-agile/">outils de gestion agile</a>, Tuleap inclut d’autres fonctionnalités pour le <a href="https://www.tuleap.org/fr/produit/fonctionnalites/developpement-continu/">développement logiciel collaboratif</a>, la <a href="https://www.tuleap.org/fr/produit/fonctionnalites/gestion-tests/">gestion des tests</a>, la gestion des documents, etc.<br>
Jira est propriétaire et sera bientôt uniquement disponible dans le <em>cloud</em>, d’après un <a href="https://www.atlassian.com/blog/announcements/journey-to-cloud">communiqué récent</a>. Tuleap est libre (licence GPL), installable sur un serveur ou disponible en tant que service dans le <em>cloud</em>, pour ceux qui ne veulent pas s’occuper de l’installation ou de la maintenance.</p>
<p>Pour ceux qui souhaitent basculer de Jira vers Tuleap, la nouvelle fonctionnalité permet d’importer les tâches, <em>stories</em> et bogues depuis un projet Jira vers un projet Tuleap. En quelques clics, un nouveau <em>tracker</em> Tuleap est créé avec les entrées.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://www.tuleap.org/moving-issues-from-jira-to-tuleap/" hreflang="en" href="https://linuxfr.org/redirect/107217">Tutoriel « Déplacer des issues de Jira vers Tuleap »</a></li><li>lien nᵒ 2 : <a title="https://docs.tuleap.org/user-guide/trackers/administration/tracker-creation.html#create-from-jira-issue" hreflang="en" href="https://linuxfr.org/redirect/107218">Documentation dans Tuleap</a></li></ul><div><h2 id="toc-limportation-se-déroule-en-quatre-étapes">L’importation se déroule en quatre étapes</h2>
<ol>
<li>côté Jira, les utilisateurs rendent leurs adresses de courriel publiquement lisibles (pour que la corrélation entre les utilisateurs puissent être réalisée) ;</li>
<li>depuis Tuleap, l’administrateur du projet Jira importe les <em>issues</em> en créant un nouveau <em>tracker</em> Tuleap dans son projet (on se connecte à l’API Jira via un <em>user API Token</em> et l’adresse de la plate‑forme Jira) ;</li>
<li>Tuleap importe automatiquement en asynchrone les <em>issues</em> en répliquant les données des champs de type texte, chaîne de caractères, date, boîte de sélection, statut, etc., ainsi que les pièces jointes, historique et commentaires ;</li>
<li>Tuleap retrouve les utilisateurs Jira et les utilisateurs Tuleap (créateur, rapporteur, listes d’utilisateurs).</li>
</ol>
<h3 id="toc-en-exemple-voici-le-résultat-sous-forme-de-kanban-des-issues-jira-importées-danstuleap">En exemple, voici le résultat sous forme de Kanban des <em>issues</em> Jira importées dans Tuleap</h3>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f7777772e74756c6561702e6f72672f77702d636f6e74656e742f75706c6f6164732f323032302f31312f6973737565732d66726f6d2d6a6972612d6b616e62616e2d74756c6561702e706e67/issues-from-jira-kanban-tuleap.png" alt="Kanban des issues importées depuis Jira dans Tuleap" title="Source : https://www.tuleap.org/wp-content/uploads/2020/11/issues-from-jira-kanban-tuleap.png"></p>
<p>Si vous avez besoin d’aide, vous pouvez posez vos questions dans la <a href="https://chat.tuleap.org/home">messagerie instantanée de la communauté Tuleap</a>.</p>
</div><div><a href="https://linuxfr.org/news/migration-de-jira-a-tuleap-nouvelle-fonctionnalite.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/122115/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/migration-de-jira-a-tuleap-nouvelle-fonctionnalite#comments">ouvrir dans le navigateur</a>
</p>
ManonMXavier TeyssierDavy DefaudBenoît Sibaudhttps://linuxfr.org/nodes/122115/comments.atomtag:linuxfr.org,2005:News/391822020-08-27T15:53:17+02:002020-09-01T09:37:44+02:00Bogues de logiciel et bogues de management : 737 Max et autres catastrophesLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Tout le monde sait ce qu’est un bogue sur un logiciel, mais un bogue au niveau management, cela existe aussi. Les conséquences peuvent être catastrophiques. Commençons par le Boeing 737 Max.</p>
<p>Le Boeing 737 Max est la dernière évolution du premier 737 sorti en 1967. Comme certaines caractéristiques ont été sensiblement modifiées, les concepteurs de l’avion ont décidé que le logiciel rattraperait les problèmes de stabilité. Par souci d’économie et pour concurrencer Airbus, Boeing a décidé d’aller vite, trop vite, en négligeant les principes fondamentaux du développement aéronautique qui ont permis à l’avion d’être le moyen de transport le plus sûr de tous.</p>
<p>Cette dépêche retrace également d’autres catastrophes, révélant les problèmes dans le processus de décision qui, bien souvent, éloigne les décideurs des alertes émises par du personnel compétent. Dans bien des organisations, les subordonnés sont incités à minimiser ce qui dérange la direction.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://spectrum.ieee.org/aerospace/aviation/how-the-boeing-737-max-disaster-looks-to-a-software-developer" hreflang="en" href="https://linuxfr.org/redirect/103942">Le Boeing 737 Max et le logiciel</a></li><li>lien nᵒ 2 : <a title="http://esamultimedia.esa.int/docs/esa-x-1819eng.pdf" hreflang="en" href="https://linuxfr.org/redirect/103953">Rapport d’enquête du vol 501 d’Ariane 5</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-boeing-737max">Boeing 737 Max</a></li>
<li>
<a href="#toc-ariane5-vol501-volinaugural">Ariane 5 vol 501 (vol inaugural)</a><ul>
<li><a href="#toc-code-source-ada-du-bo%C3%AEtier">Code source Ada du boîtier</a></li>
<li><a href="#toc-variable-biais-vertical-bv">Variable <strong>B</strong>iais <strong>V</strong>ertical <code>BV</code></a></li>
<li><a href="#toc-variable-biais-horizontal-bh">Variable <strong>B</strong>iais <strong>H</strong>orizontal <code>BH</code></a></li>
<li><a href="#toc-enqu%C3%AAtes">Enquêtes</a></li>
</ul>
</li>
<li><a href="#toc-challenger-sts-51-l">Challenger STS-51-L</a></li>
<li>
<a href="#toc-columbia-sts-107">Columbia STS-107</a><ul>
<li><a href="#toc-r%C3%A9f%C3%A9rences">Références</a></li>
</ul>
</li>
<li><a href="#toc-le-vasa">Le Vasa</a></li>
<li><a href="#toc-autres-catastrophes-similaires">Autres catastrophes similaires</a></li>
<li><a href="#toc-anneau-de-fer-martel%C3%A9">Anneau de fer martelé</a></li>
<li><a href="#toc-le-guide-de-terrain-pour-comprendre-lerreurhumaine">Le Guide de terrain pour comprendre « l’erreur humaine »</a></li>
<li><a href="#toc-voir-aussi-dautres-bogues">Voir aussi d’autres bogues</a></li>
<li><a href="#toc-et-dans-votre-organisation">Et dans votre organisation ?</a></li>
</ul>
<h2 id="toc-boeing-737max">Boeing 737 Max</h2>
<p>L’avion en est à sa sixième version. Il a été rallongé et équipé de réacteurs plus lourds et surtout plus gros, qui, s’ils avaient été mis au même endroit, auraient raclé le sol. Comme on ne pouvait pas allonger le train d’atterrissage, il a fallu réaliser de nouveaux mâts de réacteur pour avancer et relever les moteurs. Le centrage, le centre de poussée et l’aérodynamique de l’avion ont été de ce fait sérieusement modifiés, ce qui change notablement son comportement en vol. L’avion a tendance à cabrer. Pour corriger cela un capteur d’assiette a été ajouté à l’avion, et un logiciel devait renvoyer le nez de l’avion vers le sol pour compenser. </p>
<p>Le logiciel était censé corriger le problème de conception. Ce logiciel était un pis‑aller qui a été mal étudié. Il est anormal qu’un système de commandes de vol électriques doive avoir besoin d’être désactivé.</p>
<p>La bonne démarche eut été de modifier les points d’ancrage des ailes, voire de les redessiner, tout comme le plan de dérive. En fait, c’était un nouvel avion qu’il fallait créer et qualifier. Or, Boeing voulait rattraper son retard sur l’<a href="https://fr.wikipedia.org/wiki/Airbus_A320#A320neo_(A319,_A320_et_A321)" title="New Engine Option">A320neo</a> qui se vendait bien. Et l’une des raisons était l’absence de formation supplémentaire pour les pilotes.</p>
<p>Pour compléter le tableau, Boeing n’a utilisé qu’un seul capteur d’incidence (pas de redondance). L’existence même du logiciel a été cachée aux pilotes, ainsi que le moyen de le désactiver en cas de problème, pour éviter de devoir faire une formation longue à ce nouvel avion et faire croire qu’il se pilotait exactement comme un 737 classique. Le but était d’avoir les mêmes avantages que l’A320neo.</p>
<p><em>Business Insider</em> rapporte que lors d’une réunion plénière, un responsable <a href="https://www.businessinsider.fr/le-logiciel-du-boeing-737-max-aurait-ete-concu-par-des-interimaires-sous-payes/">avait déclaré</a> que « les ingénieurs expérimentés n’étaient plus nécessaires dans l’entreprise » et qu’après l’incident, un porte‑parole a déclaré que « la sécurité était toujours au centre des préoccupations ».</p>
<p>Bilan : 346 morts, deux avions perdus, déjà un milliard de dollars de perte.</p>
<p>Boeing <a href="https://www.businessinsider.fr/boeing-est-maintenant-oblige-de-garer-ses-737-max-inutilises-sur-le-parking-des-employes/">serait obligé de garer ses avions</a> cloués au sol sur le parking de ses employés.</p>
<p><em><a href="https://www.nytimes.com/2019/07/27/business/boeing-737-max-faa.html">The Roots of Boeing’s 737 Max Crisis: A Regulator Relaxes Its Oversight</a></em>.</p>
<p><em><a href="https://boingboing.net/2019/12/02/razor-sharp-metal-shavings.html">Veteran Boeing manager was transferred to 787 production; based on he saw there, he won’t fly in a Dreamliner and begs his family not to</a></em>.</p>
<p><a href="https://embarque.developpez.com/actu/296371/Le-cauchemar-du-737-MAX-ne-cesse-de-s-aggraver-un-rapport-accablant-des-enqueteurs-de-la-Chambre-US-montre-la-pire-defaillance-de-securite-dans-l-avion-cloue-au-sol-a-cause-des-problemes-logiciels/">Le cauchemar du 737 Max ne cesse de s’aggraver</a>.</p>
<h2 id="toc-ariane5-vol501-volinaugural">Ariane 5 vol 501 (vol inaugural)</h2>
<p>La conception de la fusée Ariane 5 s’est basée sur des éléments d’Ariane 4, dont le boîtier des mesures de navigation (centrale inertielle). Notons qu’Ariane 4 était le lanceur réputé le plus fiable de son époque avec 97 % de succès sur quinze ans de service (116 lancements).</p>
<p>Afin de valider l’ensemble complet d’un système, l’Aérospatiale avait l’habitude de réaliser des « chaînes sur table », c’est‑à‑dire un montage en laboratoire de tous les équipements de la fusée reliés à des simulateurs de stimuli. Dans le cas d’Ariane 5, l’Aérospatiale l’avait budgétisé à 800 000 francs. Mais le CNES pensait que l’Aérospatiale voulait réaliser ces « chaînes sur table » uniquement pour avoir plus de rentrées financières. Pourtant, ce n’était pas un test optionnel, l’Aérospatiale avait bien cette pratique sur tous ses projets spatiaux. Donc, pour faire des économies, le CNES a décidé la réutilisation de certains éléments d’Ariane 4, la calibration d’Ariane 4 et l’absence des « chaînes sur table ».</p>
<p>Le résultat a été l’explosion de la fusée après quarante secondes de vol (vidéos <a href="https://www.youtube.com/watch?v=fCnO-UYF3co" title="La vidéo du vol inaugural Ariane 5 le 4 juin 1996">1</a> et <a href="https://www.youtube.com/watch?v=PK_yguLapgA">2</a>). Heureusement, le <a href="https://fr.wikipedia.org/wiki/Vol_501_d%27Ariane_5">vol 501 d’Ariane 5</a> n’a pas fait de victimes, mais a entraîné un retard de plus d’un an sur le programme. L’économie des « chaînes sur table » de 800 000 francs, a coûté mille fois plus, 800 millions de francs !</p>
<p>Et effectivement, les « chaînes sur table » effectuées par la suite ont montré la parfaite reproductibilité du phénomène. L’accélération d’Ariane 5 étant cinq fois plus élevée que celle d’Ariane 4, la valeur <em>accélération</em> est copiée dans un registre trop petit, ce qui provoque une interruption logicielle « <em>integer overflow</em> ». Le pire, c’est que cette valeur n’était pas utile dans le cadre d’Ariane 5 !</p>
<blockquote>
<p><em>4, 3, 2, unité, feu… allumage… décollage.</em></p>
</blockquote>
<p>Dès la phase d’accélération, les deux boîtiers issus d’Ariane 4 connaissent tous les deux l’interruption logicielle « <em>integer overflow</em> ». Et par conséquent, le gestionnaire d’interruption par défaut effectue un <em>autotest</em>, c’est‑à‑dire qu’il vérifie le bus de données en envoyant alternativement des <code>0x5555</code> et des <code>0xAAAA</code>. Le modèle interne simulé fonctionne bien, quant à lui, mais le système de vote à la majorité donne raison aux deux boîtiers issues d’Ariane 4.</p>
<blockquote>
<p><em>Tous les paramètres propulsifs sont normaux et la trajectoire est normale</em>.</p>
</blockquote>
<p>Le pilotage automatique prend les commandes à la trente‑septième seconde, il pense que les données qui circulent sur le bus sont des données valides de navigation et procède à une correction extrême de la trajectoire. Ce braquage brutal exerce une pression aérodynamique très élevée. Une partie de la structure de la fusée se désolidarise, ce qui déclenche son auto‑destruction. </p>
<p>Notons que ces boîtiers sont utiles pour réaliser des mesures quelques secondes avant le décollage, mais après le décollage, ils ne sont plus d’aucune utilité. Ces boîtiers sont pourtant restés actifs pendant le décollage car c’était une exigence d’Ariane 4. Leur désactivation était prévue 40 secondes après le décollage, soit quelques secondes après le braquage de la fusée. Depuis, il y a une vraie chasse au code mort (le code inutile) dans les logiciels embarqués critiques.</p>
<h3 id="toc-code-source-ada-du-boîtier">Code source Ada du boîtier</h3>
<p><img src="//img.linuxfr.org/img/687474703a2f2f6f6c696272652e6769746875622e696f2f4772656174546970732f756e69742d746573742f6275672d417269616e652d3530315f62792d4a65616e4a6163717565734c6576792d494e5249412d323031302e6a7067/bug-Ariane-501_by-JeanJacquesLevy-INRIA-2010.jpg" alt="Scan du code source Ada du SRI (Système de Référence Inertielle)" title="Scan du code source Ada du SRI (Système de Référence Inertielle) | Source : http://olibre.github.io/GreatTips/unit-test/bug-Ariane-501_by-JeanJacquesLevy-INRIA-2010.jpg"></p>
<h3 id="toc-variable-biais-vertical-bv">Variable <strong>B</strong>iais <strong>V</strong>ertical <code>BV</code>
</h3>
<p>Nous avons bien le test des bornes -32768..32767 avant copie dans le registre 16 bits :</p>
<pre><code class="ada"><span class="n">L_M_BV_32</span> <span class="p">:=</span> <span class="n">TBD</span><span class="p">.</span><span class="n">T_ENTIER_32S</span> <span class="p">((</span><span class="mf">1.0</span><span class="o">/</span><span class="n">C_M_LSB_BV</span><span class="p">)</span> <span class="o">*</span>
<span class="n">G_M_INFO_DERIVE</span><span class="p">(</span><span class="n">T_ALG</span><span class="p">.</span><span class="n">E_BV</span><span class="p">));</span>
<span class="kr">if</span> <span class="n">L_M_BV_32</span> <span class="o">></span> <span class="mi">32767</span> <span class="kr">then</span>
<span class="n">P_M_DERIVE</span><span class="p">(</span><span class="n">T_ALG</span><span class="p">.</span><span class="n">E_BV</span><span class="p">)</span> <span class="p">:=</span> <span class="mh">16#7FFF#</span><span class="p">;</span>
<span class="kr">elsif</span> <span class="n">L_M_BV_32</span> <span class="o"><</span> <span class="o">-</span><span class="mi">32768</span> <span class="kr">then</span>
<span class="n">P_M_DERIVE</span><span class="p">(</span><span class="n">T_ALG</span><span class="p">.</span><span class="n">E_BV</span><span class="p">)</span> <span class="p">:=</span> <span class="mh">16#8000#</span><span class="p">;</span>
<span class="kr">else</span>
<span class="n">P_M_DERIVE</span><span class="p">(</span><span class="n">T_ALG</span><span class="p">.</span><span class="n">E_BV</span><span class="p">)</span> <span class="p">:=</span> <span class="n">UC_16S_EN_16NS</span><span class="p">(</span><span class="n">TDB</span><span class="p">.</span><span class="n">T_ENTIER_16S</span><span class="p">(</span><span class="n">L_M_BV_32</span><span class="p">));</span>
<span class="kr">end</span> <span class="kr">if</span><span class="p">;</span></code></pre>
<h3 id="toc-variable-biais-horizontal-bh">Variable <strong>B</strong>iais <strong>H</strong>orizontal <code>BH</code>
</h3>
<p>La valeur est copiée directement dans le registre 16 bits sans protection. Le <a href="http://www.astrosurf.com/luxorion/astronautique-accident-ariane-v501.htm">rapport d’enquête</a> indique que certaines variables n’étaient pas protégées pour éviter que la charge du processeur dépasse les 80 %. Aucune information n’a été trouvée pour justifier de protéger telle variable plutôt qu’une autre.</p>
<pre><code class="ada"><span class="n">P_M_DERIVE</span><span class="p">(</span><span class="n">T_ALG</span><span class="p">.</span><span class="n">E_BH</span><span class="p">)</span> <span class="p">:=</span> <span class="n">UC_16S_EN_16NS</span> <span class="p">(</span><span class="n">TDB</span><span class="p">.</span><span class="n">T_ENTIER_16S</span>
<span class="p">((</span><span class="mf">1.0</span><span class="o">/</span><span class="n">C_M_LSB_BH</span><span class="p">)</span> <span class="o">*</span>
<span class="n">G_M_INFO_DERIVE</span><span class="p">(</span><span class="n">T_ALG</span><span class="p">.</span><span class="n">E_BH</span><span class="p">)));</span></code></pre>
<h3 id="toc-enquêtes">Enquêtes</h3>
<p>Un contributeur de <em>LinuxFr.org</em> qui travaillait au projet Ariane 5, nous indique que seulement quelques heures après l’échec du lancement, les équipes d’Aérospatiale avaient déjà repéré des signaux d’erreur en ASCII qui circulaient sur le bus de données et que le pilotage automatique avait pris cela pour des données numériques valides.</p>
<p>Le CNES met immédiatement en place une commission d’enquête qui donne ses conclusions un mois après. Le <a href="http://deschamp.free.fr/exinria/divers/ariane_501.html">rapport officiel</a> démontre que les concepteurs du calculateur de la trajectoire ont volontairement exclu la spécificité d’Ariane 5. Lire aussi <a href="http://www.math.umn.edu/%7Earnold/disasters/ariane5rep.html">cette version anglaise</a> et cette <a href="http://www.rvs.uni-bielefeld.de/publications/Reports/ariane.html">autre version anglaise</a>.</p>
<p>Cette commission d’enquête officielle est composée d’ingénieurs en logiciel, et conclut à un problème logiciel. Deux autres enquêtes indépendantes remettent davantage en cause les erreurs de gestion du programme (le lien vers le <a href="http://cmpe.emu.edu.tr/chefranov/Cmps201-fall2011/Notes/Ariane5failure.pdf">PDF</a> est cassé). Gérard Le Lann (INRIA) conclut à un <a href="https://hal.inria.fr/inria-00073613/document">problème d’intégration système</a>. Jacques‑Louis Lions parle d’<a href="https://zoo.cs.yale.edu/classes/cs422/2010/bib/lions96ariane5.pdf">erreur de spécification et de conception logicielles</a>. Quant à Mark Dowson, il insiste sur l’<a href="https://www.deepdyve.com/lp/association-for-computing-machinery/the-ariane-5-software-failure-jZY4texaSd">environnement de travail</a> comme étant les racines de l’échec :</p>
<ol>
<li>carriérisme des managers et aspirations politiques de leurs décisions ;</li>
<li>pressions sur les budgets ;</li>
<li>pressions sur les délais ;</li>
<li>culture du « pas cassé, pas corrigé » (<em>If it’s not broken don’t fix it</em>).</li>
</ol>
<p>Alors, le problème d’Ariane 5 était‑il un bogue logiciel comme en conclut la commission d’enquête officielle, ou alors un bogue managérial ? Dans tous les cas, aucun manager n’a été inquiété. En revanche, les développeurs, eux, ont <a href="https://fr.wiktionary.org/wiki/avoir_du_pain_sur_la_planche#Locution_verbale">eu du pain sur la planche</a>.</p>
<h2 id="toc-challenger-sts-51-l">Challenger STS-51-L</h2>
<p>L’<a href="https://fr.wikipedia.org/wiki/accident%20de%20la%20navette%20spatiale%20Challenger" title="Définition Wikipédia">accident de la navette spatiale Challenger</a> a aussi pour origine la volonté de faire des économies sur le délai et le budget. Les personnes prenant ces décisions ont tendance à ignorer les alertes des ingénieur·e·s et physicien·ne·s car n’apprécient pas être remises en cause. C’est malheureusement un comportement humain répandu, qui devient un défaut fatal quand on a un poste de grande responsabilité.</p>
<p>L’accident a eu lieu 73 secondes après le décollage, entraînant la mort de l’équipage dont une institutrice qui devait donner un cours depuis l’espace et devenir la première « passagère de l’espace », tout un symbole. La médiatisation de ce coup de communication rendit le drame plus insoutenable car 48 % des élèves américains de neuf à treize ans regardaient le décollage depuis leur école.</p>
<p>L’équipage a probablement survécu à la désintégration du vaisseau et serait dans ce cas décédé lors de l’impact de la cabine avec la mer. La navette spatiale américaine avait été conçue sans système de sauvetage au décollage, en se fondant sur l’hypothèse que la navette spatiale devait abaisser le risque couru par les astronautes au même niveau que celui des passagers des avions. </p>
<p>L’accident a entraîné la mort de sept personnes, la perte du vaisseau, et une interruption de trente‑deux mois du programme de la navette.</p>
<p>En complément : la vidéo de Stardust « <a href="https://www.youtube.com/watch?v=59n4bMjL_xc">La destruction de la navette <em>Challenger</em></a> » (durée : 14 minutes).</p>
<h2 id="toc-columbia-sts-107">Columbia STS-107</h2>
<p>Le 1ᵉʳ février 2003, après quinze jours passés en orbite basse, <a href="https://fr.wikipedia.org/wiki/Accident_de_la_navette_spatiale_Columbia">la navette spatiale américaine <em>Columbia</em> se désintègre lors de sa rentrée dans l’atmosphère terrestre</a>.</p>
<p>En août 2003, dans son rapport final, la Commission d’enquête sur l’accident de <em>Columbia</em> détermine que la cause directe de l’accident fut l’impact sur l’aile gauche de la navette d’un morceau de mousse isolante qui s’était détaché du réservoir externe lors du décollage. Le bouclier thermique de la navette étant endommagé, le vaisseau fut détruit lors de la rentrée atmosphérique en retour de mission.</p>
<p>Mais les causes de l’accident sont aussi d’ordre organisationnel. Ce problème de débris de mousse isolante était déjà connu des ingénieurs, mais les missions ont continué parce que les impacts étaient considérés comme inévitables et sans solution (« S’il n’y a pas de solution, c’est qu’il n’y a pas de problème. » — Jacques Rouxel <em>in</em> <em>Les shadoks</em>).</p>
<p>Aussi, après le décollage, plusieurs responsables veulent obtenir des images de la navette pour étudier les dégâts potentiellement provoqués par l’impact, mais se voient refuser leurs demandes pour des raisons de délais ou de budget. Ils se voient également reprocher d’avoir contourné la hiérarchie et de ne pas respecter la bureaucratie de la NASA.</p>
<p>Résultat : sept morts supplémentaires, une navette supplémentaire détruite, interruption du programme de la navette pendant vingt‑neuf mois, et suspension de la construction de l’ISS.</p>
<h3 id="toc-références">Références</h3>
<ul>
<li>
<a href="http://www.securiteaerienne.com/columbia-sts-107-chronique-dune-catastrophe-annoncee/">Columbia STS-107 – Chronique d’une catastrophe annoncée</a> ;</li>
<li>vidéo de Stardust « <a href="https://www.youtube.com/watch?v=Nelzv2NJqqQ">La destruction de la navette Columbia</a> » (durée : 13 minutes).</li>
</ul>
<h2 id="toc-le-vasa">Le Vasa</h2>
<p>Cet énorme vaisseau, le <em><a href="https://fr.wikipedia.org/wiki/Vasa" title="Définition Wikipédia">Vasa</a></em>, a sombré en 1628 lors de sa sortie inaugurale après seulement 1 600 m de navigation. On ne pouvait pas incriminer le logiciel à l’époque et même si, près de quatre siècles plus tard, la perception d’une construction bâclée et d’un chantier désorganisé sont à l’origine du <a href="https://fr.wikipedia.org/wiki/Syndrome_de_Vasa">syndrome de Vasa</a>.</p>
<p>Les responsabilités sont difficiles à cerner et semblent diluées tout au long de la construction. Beaucoup d’éléments semblent concourir à l’aboutissement d’un bateau instable :</p>
<ul>
<li>les demandes de modifications faites par le roi de Suède pendant la construction, notamment les 72 canons qui étaient trop nombreux pour tenir sur un seul pont ;</li>
<li>les changements de direction de la construction navale ;</li>
<li>les délais et les questions économiques du fait de la perte de dix navires en une seule tempête ;</li>
<li>les tests de navigabilité négligés.</li>
</ul>
<p>Résultat : trente à cinquante personnes périrent avec le navire et, par ailleurs, ce naufrage du <em>Vasa</em> fut un véritable désastre financier pour le petit État suédois.</p>
<h2 id="toc-autres-catastrophes-similaires">Autres catastrophes similaires</h2>
<ul>
<li>mauvaises décisions lors de la construction du dirigeable britannique <a href="https://www.airships.net/blog/british-airship-r101-crashes-killing-48-day-1930/">R101</a> qui s’écrase à 80 km au nord de Paris ;</li>
<li>volonté des dirigeants d’arriver en avance et <a href="https://fr.wikipedia.org/wiki/Titanic">naufrage du <em>Titanic</em></a> ;</li>
<li>déni d’erreur du commandant de bord et mauvaise procédures de secours lors du <a href="https://fr.wikipedia.org/wiki/Naufrage_du_Sewol">naufrage du <em>Sewol</em></a>.</li>
</ul>
<h2 id="toc-anneau-de-fer-martelé">Anneau de fer martelé</h2>
<p>L’histoire commence avec l’ambitieuse construction du <a href="https://fr.wikipedia.org/wiki/pont%20de%20Qu%C3%A9bec" title="Définition Wikipédia">pont de Québec</a> en 1903 : le plus long pont de type porte‑à‑faux au monde. La construction débute sous la direction d’un ingénieur originaire des États‑Unis (<a href="https://fr.wikipedia.org/wiki/Theodore%20Cooper" title="Définition Wikipédia">Theodore Cooper</a>). À cause d’erreurs de calcul, le poids réel du pont excède sa capacité portante. Des problèmes furent remarqués par les ingénieurs canadiens, mais la direction ne tient pas compte de la gravité de la situation. En 1907, un ingénieur responsable demande l’arrêt complet des travaux, mais les travaux continuèrent. Deux jours après, 20 000 tonnes d’acier croulent dans le fleuve, et soixante‑seize travailleurs (sur cent) sont tués. À marée basse, la ferraille provenant de cet effondrement est visible sur la rive du fleuve. Le pont connaît un second effondrement en 1916, provoquant treize décès. L’année suivante, le pont est enfin achevé.</p>
<p>Suite à ces incidents, en 1922, l’ingénieur <a href="https://en.wikipedia.org/wiki/H._E._T._Haultain">Haultain</a> propose un rite d’engagement solennel qui oblige les ingénieurs à un comportement professionnel exemplaire. La même année, la Société des Sept Gardiens est créée et procède à sa première cérémonie en 1925 en remettant un <a href="https://fr.wikipedia.org/wiki/Anneau_de_fer_martel%C3%A9">anneau de fer martelé</a> à chaque ingénieur. Si l’ingénieur abandonne son serment, il doit rendre l’anneau.</p>
<h2 id="toc-le-guide-de-terrain-pour-comprendre-lerreurhumaine">Le Guide de terrain pour comprendre « l’erreur humaine »</h2>
<p>Pour aller plus loin, le livre <em><a href="https://www.oreilly.com/library/view/the-field-guide/9781317031833/">The Field Guide to Understanding “Human Error”</a></em>, troisième édition par Sidney Dekker (CRC Press, novembre 2017, ISBN 9781317031833).</p>
<h2 id="toc-voir-aussi-dautres-bogues">Voir aussi d’autres bogues</h2>
<ul>
<li>1980 — Le système <a href="https://fr.wikipedia.org/wiki/Commandement_de_la_d%C3%A9fense_a%C3%A9rospatiale_de_l%E2%80%99Am%C3%A9rique_du_Nord" title="commandement de la défense aérospatiale de l’Amérique du Nord">NORAD</a> déclenche, à deux reprises, à trois jours d’intervalle, une <a href="https://en.wikipedia.org/wiki/North_American_Aerospace_Defense_Command#False_alarms">fausse alerte</a> d’attaque nucléaire, et, à chaque fois, les bombardiers chargés de bombes nucléaires décollent pour la contre‑attaque. En fait, le logiciel ne gérait pas la défaillance électrique.</li>
<li>1983 — Un satellite soviétique déclenche une fausse alerte d’une attaque de missiles, mais heureusement, l’officier russe n’y croit pas.</li>
<li>1983 — Le <a href="https://fr.wikipedia.org/wiki/Vancouver%20Stock%20Exchange" title="Définition Wikipédia">Vancouver Stock Exchange</a> corrige son index de <a href="https://en.wikipedia.org/wiki/Vancouver_Stock_Exchange#Rounding_errors_on_its_Index_price">525 à 1099 à cause d’une erreur d’arrondi</a> passée inaperçue pendant quelques années.</li>
<li>1985 — La NASA <a href="https://earthobservatory.nasa.gov/Features/RemoteSensingAtmosphere/remote_sensing5.php">ne détecte aucun trou d’ozone</a> pendant sept ans car les grandes variations dans les mesures ne sont pas prises en compte.</li>
<li>1993 — Bogue du Pentium sur les nombres flottants.</li>
<li>1998 — Désintégration de <a href="https://fr.wikipedia.org/wiki/Mars_Climate_Orbiter#Perte_de_la_sonde_(23_septembre_1999)">Mars Climate Orbiter</a> car une fonction utilise l’unité <a href="https://fr.wikipedia.org/wiki/Livre-force">livre‑force</a> (<a href="https://fr.wikipedia.org/wiki/Unit%C3%A9s_de_mesure_anglo-saxonnes">système anglo‑saxon</a>) au lieu du <a href="https://fr.wikipedia.org/wiki/Newton_(unit%C3%A9)">newton</a>. Pourtant, les projets de la NASA sont censés utiliser le <a href="https://fr.wikipedia.org/wiki/Syst%C3%A8me_m%C3%A9trique">système métrique</a>.</li>
<li>2010 — Toyota rappelle un million de véhicules mais ce n’est pas la mécanique qui est en cause, plutôt le <a href="//linuxfr.org/news/encore-un-exemple-de-code-spaghetti-toyota">code spaghetti bourré de négligences</a>.</li>
<li>2012 — <a href="https://fr.wikipedia.org/wiki/Knight%20Capital%20Group" title="Définition Wikipédia">Knight Capital Group</a> met en production un automate de <em>trading</em> haute fréquence qui exécute, par erreur, un code de test faisant perdre à l’entreprise 440 millions de dollars en 45 minutes, soit 90 millions de plus que son capital, plongeant son cours de bourse de 75 %. Pour l’anecdote, KCG a réussi à renaître de ses cendres en levant 400 millions (4 jours après), puis revend ses logiciels <em>KCG Hotspot</em> à BATS pour 365 millions (2015), enfin <em>KCG Holdings</em> est valorisé à 1,4 milliard (2017) lors du rachat par Virtu Financial !</li>
<li>2014 — Apple a dans son code source <a href="https://en.wikipedia.org/wiki/Unreachable_code#goto_fail_bug">deux lignes successives « <code>goto fail</code> »</a> ce qui a conduit à l’ajout de l’option <a href="https://developers.redhat.com/blog/2016/02/26/gcc-6-wmisleading-indentation-vs-goto-fail/"><code>-W misleading-indentation</code></a> à <a href="//linuxfr.org/news/sortie-de-gcc-6#nouvelles-informations-sur-les-erreurs-et-alertes-%C3%A0-la-compilation">GCC 6</a> (lire aussi le <a href="//linuxfr.org/users/flagos/journaux/apple-le-ssl-les-goto-et-les-accolades">journal</a>).</li>
<li>2015 — Valve Steam dont son script d’installation pouvait effacer tout le <code>$HOME</code> sous GNU/Linux.</li>
</ul>
<h2 id="toc-et-dans-votre-organisation">Et dans votre organisation ?</h2>
<p>Reconnaissez‑vous des aspects de vos projets ? Partagez vos propres expériences dans les commentaires.</p>
</div><div><a href="https://linuxfr.org/news/bogues-de-logiciel-et-bogues-de-management-737-max-et-autres-catastrophes.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/117008/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/bogues-de-logiciel-et-bogues-de-management-737-max-et-autres-catastrophes#comments">ouvrir dans le navigateur</a>
</p>
palm123OliverThomas DebessePierre JarillonNicolas BoulayDavy DefaudYves BourguignonSnarkAnonymeKerroBenoît SibaudpapapPierre TramalYsabeau 🧶 🧦StormZeroHeurewindu.2b_seb_BlackknightMarcomatteliBAudFabrice MoussetTintinLhttps://linuxfr.org/nodes/117008/comments.atomtag:linuxfr.org,2005:News/398542020-05-20T10:55:07+02:002020-05-20T12:27:58+02:00Harbor 2.0Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Harbor est un <em>registry</em> d’images de conteneurs open source qui sécurise les images à l’aide de contrôles d’accès basés sur des rôles, d’analyses des images à la recherche de vulnérabilités et de signature d’images comme étant de confiance. Harbor a comme but d’aider à gérer de manière cohérente et sécurisée les images sur des plates‑formes <em>cloud</em> comme Kubernetes et Docker.</p>
<p>Dans sa version 2.0 qui vient de sortir, Harbor est maintenant totalement compatible OCI (<em><a href="https://www.opencontainers.org/">Open Container Initiative</a></em>). Ainsi, il permet de stocker les images de vos conteneurs ou tout objet compatible OCI comme les <em>Helm Charts</em> en version 3. L’avantage de cette compatibilité avec OCI est que cela évite de devoir avoir un système spécifique pour chaque type d’objet.</p>
<p>Harbor vous permet donc de stocker vos images privées en local ou d’en faire un cache pour éviter des problèmes de réseau au téléchargement. Il permet aussi de d’analyser des images pour vérifier qu’elles ne contiennent pas de failles de sécurité connues et de vérifier les signatures pour ne distribuer que des images signées.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f64746235707a737763697431652e636c6f756466726f6e742e6e65742f6173736574732f696d616765732f70726f647563745f6c6f676f732f69636f6e5f766d776172655f686172626f724032782e706e67/icon_vmware_harbor@2x.png" alt="Logo Harbor" title="Source : https://dtb5pzswcit1e.cloudfront.net/assets/images/product_logos/icon_vmware_harbor@2x.png"></p>
</div><ul><li>lien nᵒ 1 : <a title="https://goharbor.io/blog/harbor-2.0/" hreflang="en" href="https://linuxfr.org/redirect/106304">Annonce de sortie</a></li></ul><div><h2 id="toc-nouveautés">Nouveautés</h2>
<h3 id="toc-oci">OCI</h3>
<p>Harbor est maintenant compatible avec le format et les API OCI (<em>Open Container Initiative</em>), ce qui lui permet de stocker n’importe quelle donnée du moment qu’elle est compatible avec cette API. Par exemple, cela veut dire que les <em>Helm Charts</em> et les images de conteneurs sont stockés de la même manière.</p>
<p>OCI apporte aussi un index qui permet de spécifier la même image pour différentes architectures. Cela signifie qu’il n’y a plus besoin de le spécifier explicitement, c’est le client qui choisira ce dont il a besoin en fonction des paramètres définis.</p>
<h3 id="toc-analyse-dimage">Analyse d’image</h3>
<p><a href="https://github.com/aquasecurity/trivy">Trivy</a> remplace Clair comme outil d’analyse par défaut. Clair reste disponible. Un des gros avantages de Trivy est d’analyser toutes les couches des images au lieu de seulement la dernière couche. Cela permet de trouver des vulnérabilités dans des bibliothèques compilées en statique.</p>
<h3 id="toc-comptes-pour-robot">Comptes pour robot</h3>
<p>Les comptes pour robot, dédiés à être utilisés dans des scripts ou comme <em>credentials</em> pour tirer des images, peuvent désormais expirer de manière individuelle et non plus uniquement de façon globale et, dans le futur, il sera possible d’avoir le même compte pour plusieurs projets.</p>
<h3 id="toc-chiffrement">Chiffrement</h3>
<p>Il est maintenant possible de chiffrer en SSL (d’après l’annonce, j’espère que c’est du TLS en vrai) la communication entre les différents composants d’Harbor.</p>
<h3 id="toc-webhook">Webhook</h3>
<p>Il est à présent possible de déclencher les <em><a href="https://en.wikipedia.org/wiki/Webhook">Webhooks</a></em> de manière individuelle et l’intégration avec Slack est fournie de base.</p>
</div><div><a href="https://linuxfr.org/news/harbor-2-0.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/120432/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/harbor-2-0#comments">ouvrir dans le navigateur</a>
</p>
Xavier ClaudetisaactheojouedubanjoXavier TeyssierDavy DefaudYsabeau 🧶 🧦https://linuxfr.org/nodes/120432/comments.atomtag:linuxfr.org,2005:News/396672020-01-30T16:23:55+01:002020-02-01T17:21:03+01:00NoComprendo, version 1.0Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>NoComprendo est une application de commandes vocales pour GNU/Linux basée sur l’environnement Qt et utilisant la bibliothèque PocketSphinx pour la partie reconnaissance vocale.<br>
<img src="//img.linuxfr.org/img/687474703a2f2f62652e726f6f742e667265652e66722f736f66742f6e6f636f6d7072656e646f2f73637265656e73686f74732f6d61696e2d66756c6c2d66722e706e67/main-full-fr.png" alt="NoComprendo" title="Source : http://be.root.free.fr/soft/nocomprendo/screenshots/main-full-fr.png"></p>
</div><ul><li>lien nᵒ 1 : <a title="https://linuxfr.org/users/be-root/journaux/nocomprendo-version-1-0" hreflang="fr" href="https://linuxfr.org/redirect/105651">Journal à l’origine de la dépêche</a></li><li>lien nᵒ 2 : <a title="http://be.root.free.fr/?soft=nocomprendo&menu=desc" hreflang="fr" href="https://linuxfr.org/redirect/105652">Site de NoComprendo</a></li><li>lien nᵒ 3 : <a title="https://github.com/cmusphinx/pocketsphinx" hreflang="en" href="https://linuxfr.org/redirect/105653">PocketSphinx</a></li><li>lien nᵒ 4 : <a title="https://linuxfr.org/users/be-root/journaux/nocomprendo-la-commande-vocale-pour-linux" hreflang="fr" href="https://linuxfr.org/redirect/105654">Journal précédent sur NoComprendo</a></li><li>lien nᵒ 5 : <a title="https://software.opensuse.org/download.html?project=home:be-root:nocomprendo&package=nocomprendo" hreflang="en" href="https://linuxfr.org/redirect/105655">Paquets d’installation chez OBS</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-nouveaut%C3%A9s">Nouveautés</a><ul>
<li><a href="#toc-pilotage-vocal-de-nocomprendo">Pilotage vocal de NoComprendo</a></li>
<li><a href="#toc-exportation-csv-des-commandes">Exportation CSV des commandes</a></li>
<li><a href="#toc-fen%C3%AAtre-osd">Fenêtre OSD</a></li>
<li><a href="#toc-m%C3%A9mento-des-commandes">Mémento des commandes</a></li>
<li><a href="#toc-d%C3%A9placement-de-souris">Déplacement de souris</a></li>
<li><a href="#toc-la-langue-anglaise">La langue anglaise</a></li>
</ul>
</li>
<li><a href="#toc-empaquetage">Empaquetage</a></li>
<li><a href="#toc-partage-de-groupes-de-commandes">Partage de groupes de commandes</a></li>
<li><a href="#toc-suspension-du-projet">Suspension du projet</a></li>
</ul>
<p>Il y a deux mois, <a href="//linuxfr.org/users/be-root/journaux/nocomprendo-la-commande-vocale-pour-linux">je vous présentais NoComprendo</a>, un logiciel de commande vocale développé pour mes besoins personnels.</p>
<p>Le journal a été apprécié, mais j’ai eu peu de retours d’utilisateurs, la plupart concernant des problèmes de compilation. Le site <a href="https://build.opensuse.org/">Open Build Service</a> ne fournissant aucune métrique des téléchargements, je m’interroge sur le nombre d’utilisateurs potentiels. Alors, s’il existe quelques utilisateurs réguliers, un petit courriel me permettrait de me sentir moins seul à parler devant mon écran.</p>
<p>J’ai donc continué à le faire évoluer en fonctions de mes inspirations. La nouvelle version apporte son lot de corrections de bogues que je ne détaillerai pas ici. L’interface a très peu changé :<br>
<img src="//img.linuxfr.org/img/687474703a2f2f62652e726f6f742e667265652e66722f736f66742f6e6f636f6d7072656e646f2f73637265656e73686f74732f6d61696e2d66756c6c2d66722e706e67/main-full-fr.png" alt="NoComprendo" title="Source : http://be.root.free.fr/soft/nocomprendo/screenshots/main-full-fr.png"></p>
<h2 id="toc-nouveautés">Nouveautés</h2>
<h3 id="toc-pilotage-vocal-de-nocomprendo">Pilotage vocal de NoComprendo</h3>
<p>Les trois dialogues principaux de l’application sont maintenant ouvrables par méta‑commandes.<br>
Cela permet d’activer ou désactiver les groupes de commandes avec la voix (ouvrir le dialogue, puis naviguer vocalement avec les commandes associées aux touches du clavier). On peut également naviguer dans le dialogue de configuration les bras croisés. Ça demande un peu d’entraînement mais ça fonctionne très bien.</p>
<h3 id="toc-exportation-csv-des-commandes">Exportation CSV des commandes</h3>
<p>Avec un nombre de commandes qui enflait, j’ai ajouté une exportation au format CSV, avec l’idée d’imprimer un mémento. Cette exportation est toujours présente, même si une autre solution a été trouvée depuis (voir ci‑dessous).</p>
<h3 id="toc-fenêtre-osd">Fenêtre OSD</h3>
<p>Je travaille souvent avec deux écrans. NoComprendo trouve bien sa place sur l’écran de droite, mais avec seulement un seul écran, on économiserait bien la surface de la fenêtre, même réduite.</p>
<ul>
<li>Première solution : une fenêtre OSD (<em>On Screen Display</em>) pour afficher le dernier énoncé reconnu. On peut la positionner et la redimensionner où l’on veut, et modifier sa durée d’affichage.</li>
</ul>
<h3 id="toc-mémento-des-commandes">Mémento des commandes</h3>
<p>La fenêtre principale propose la liste des commandes, mais celle‑ci est difficilement accessible (changer de fenêtre, faire défiler la liste, revenir d’où l’on vient).</p>
<ul>
<li>Deuxième solution : une fenêtre plein écran de rappel des commandes, activable par la méta‑commande « <em>Reminder</em> » et qui se ferme d’un simple clic ou d’une action au clavier (par exemple en disant « clic » ou « échappe »).</li>
</ul>
<h3 id="toc-déplacement-de-souris">Déplacement de souris</h3>
<p>De nouvelles commandes permettent maintenant de lancer la souris dans huit directions, c’est le mode « glissade ». Le problème qui se pose est celui d’arrêter la glisse. La solution choisie consiste à interrompre la glisse dès le début de détection d’un énoncé. La précision n’est pas au rendez‑vous, ça dérape un peu avant l’arrêt, mais ça permet de traverser l’écran facilement. Les autres commandes de la souris sont là pour la précision finale. Le pas et la vitesse de glissade sont bien sûr configurables.</p>
<h3 id="toc-la-langue-anglaise">La langue anglaise</h3>
<p>Les modèles acoustiques et les dictionnaires phonétiques nécessaires pour la langue anglaise ont été ajoutés. J’ai l’impression que ça marche, mais mon terrible accent de grenouille fait que j’ai surtout du succès avec des énoncés comme « <em>page up</em> » et « <em>page down</em> ». Pour articuler correctement « <em>start browser</em> », j’ai besoin d’une dizaine d’essais. Il faudrait que j’essaie en mâchant du chewing‑gum.</p>
<p>Les groupes de commandes fournis en démonstration sont à améliorer et à compléter par de vrais anglophones. Pour tester l’application en anglais lancez la depuis un terminal : <code>LANGUAGE=en_US nocomprendo</code>, ou plutôt <code>LANGUAGE=en_US nocomprendo 2>/dev/null</code> pour cacher les journaux de PocketSphinx.</p>
<p>Pour l’aide en anglais, j’ai « googletranslaté » sans vergogne.</p>
<h2 id="toc-empaquetage">Empaquetage</h2>
<p>Les deux langues <em>fr_FR</em> et <em>en_US</em> sont emballées dans le même paquet. Ce n’est sûrement pas la meilleure technique, mais ça reste la solution la plus simple dans un premier temps. Une fois installés, les dictionnaires anglais et français prennent 3,2 Mio chacun. Le modèle acoustique français fait 10 Mio et l’anglais 18 Mio.</p>
<p>Par ailleurs, un paquet pour Ubuntu 18.04 LTS a été ajouté suite à la demande d’utilisateurs.</p>
<h2 id="toc-partage-de-groupes-de-commandes">Partage de groupes de commandes</h2>
<p>Si vous avez réalisé un groupe de commandes pour une application ou un environnement spécifique qui peut être utile à d’autres personnes, envoyez‑moi le fichier produit par la fonction d’exportation, je le mettrai en partage sur le site du projet. Idem pour des groupes de commandes en anglais.</p>
<h2 id="toc-suspension-du-projet">Suspension du projet</h2>
<p>Je considère ce projet comme terminé et souhaite retourner vers d’autres centres d’intérêts.<br>
Pour marquer le coup, NoComprendo passe en version 1.0.0. J’assurerai les corrections de bogues, mais je ne prévois pas de nouvelles fonctionnalités pour l’instant. Pour des traductions vers d’autres langues, proposez vos services pour traduire l’aide et les menus. Les langues disponibles pour PocketSphinx sont basées ici : <a href="https://sourceforge.net/projects/cmusphinx/files/Acoustic%20and%20Language%20Models/">CMU Sphinx</a>. Je m’occuperai volontiers de leur intégration.</p>
<hr>
<p>P.S. — Je tiens à présenter mes excuses à <a href="//linuxfr.org/users/nokomprendo-3">nokomprendo</a>, dont le pseudo collisionne avec le nom de mon programme. En cas de conflit insoluble entre nous, je propose de rebaptiser mon application GPasCompris, pour rester dans le même esprit.</p>
</div><div><a href="https://linuxfr.org/news/nocomprendo-version-1-0.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/119282/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/nocomprendo-version-1-0#comments">ouvrir dans le navigateur</a>
</p>
be.rootZeroHeurepatrick_gDavy Defaudhttps://linuxfr.org/nodes/119282/comments.atomtag:linuxfr.org,2005:News/393342019-07-09T16:28:22+02:002019-07-09T16:28:22+02:00Sortie de Datafari 4.3, moteur de recherche open source pour entrepriseLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Nous avions déjà fait une dépêche pour présenter Datafari à l’occasion de la sortie de la version 4.1 (et nous avions oublié d’annoncer la 4.2…), nous sommes ravis de communiquer sur la sortie de la 4.3. </p>
<p>Datafari est une solution de recherche pour entreprise. Cela signifie qu’elle permet aux employés de retrouver les données où qu’elles soient, quelles qu’elles soient. Plus concrètement, il s’agit de récupérer et d’indexer les données et documents depuis de nombreuses sources différentes et plusieurs formats de fichiers, et de permettre de chercher aussi bien l’intérieur des documents que leurs métadonnées.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://www.datafari.com/telechargement.html" hreflang="fr" href="https://linuxfr.org/redirect/104468">Page de téléchargement</a></li><li>lien nᵒ 2 : <a title="https://github.com/francelabs/datafari" hreflang="fr" href="https://linuxfr.org/redirect/104469">Télécharger les sources</a></li><li>lien nᵒ 3 : <a title="https://groups.google.com/forum/#!forum/datafari" hreflang="fr" href="https://linuxfr.org/redirect/104470">Le forum de discussion</a></li><li>lien nᵒ 4 : <a title="https://datafari.atlassian.net/wiki/spaces/DATAFARI/pages/1081354/Introduction" hreflang="fr" href="https://linuxfr.org/redirect/104471">La documentation</a></li><li>lien nᵒ 5 : <a title="https://www.datafari.com/" hreflang="fr" href="https://linuxfr.org/redirect/104472">Le site officiel</a></li><li>lien nᵒ 6 : <a title="https://demo.datafari.com/" hreflang="fr" href="https://linuxfr.org/redirect/104473">La démo en ligne</a></li><li>lien nᵒ 7 : <a title="https://linuxfr.org/news/datafari-4-1-moteur-de-recherche-open-source-pour-entreprise" hreflang="fr" href="https://linuxfr.org/redirect/104477">Précédente dépêche</a></li></ul><div><p><img src="//img.linuxfr.org/img/68747470733a2f2f7777772e64617461666172692e636f6d2f66696c65732f44617461666172695f4d61696e5f5365617263685f4261725f342d332e706e67/Datafari_Main_Search_Bar_4-3.png" alt="Page d’accueil de Datafari" title="Source : https://www.datafari.com/files/Datafari_Main_Search_Bar_4-3.png"></p>
<h2 id="toc-les-nouveautés-et-changements-principaux-depuis-la41-version-communautaire-libre">Les nouveautés et changements principaux depuis la 4.1 version communautaire libre</h2>
<ol>
<li>nouveau <em>widget</em> de prévisualisation permettant de voir le contenu d’un document sans ouvrir le document source (voir la capture d’écran plus bas) ;</li>
<li>extraction simple d’entités ;</li>
<li>Prise en charge du protocol SMBv2 ;</li>
<li>un nouveau menu utilisateur pour un accès simplifié aux options de recherche et aux pages d’administration ;</li>
<li>les alertes de recherche prennent en compte les facettes de recherche ;</li>
<li>optimisation des valeurs par défaut du connecteur Web simplifié, pour le rendre plus efficace ;</li>
<li>amélioration du connecteur web avec des filtres sur les balises HTML ;</li>
<li>retrait de la gestion de la sécurité (maintenant dédiée à la version Entreprise propriétaire) ;</li>
<li>mises à jour de tous les principaux composants techniques de Datafari, apportant plus de stabilité, de sécurité et de rapidité ;</li>
<li>de la correction de bogues de partout.</li>
</ol>
<h2 id="toc-comment-démarrer">Comment démarrer ?</h2>
<p>Pour démarrer tout de suite, le mieux est sans doute de suivre le <a href="https://datafari.atlassian.net/wiki/spaces/DATAFARI/pages/66125825/Quick+Start+Guide"><em>quick start guide</em></a>. Pour aller plus loin, il suffit de se balader sur la <a href="https://datafari.atlassian.net/wiki/spaces/DATAFARI/pages/1081354/Introduction">documentation Datafari</a> sur Confluence, qui couvre les usages, l’administration et le développement.</p>
<p>Pour rappel, voici les principales fonctionnalités de Datafari en tant que moteur de recherche :</p>
<h3 id="toc-que-peuton-faire-avec-datafari">Que peut‐on faire avec Datafari ?</h3>
<p>Comme dit plus haut, c’est un moteur de recherche pour entreprise. Ses objectifs sont différents d’un moteur de recherche Web, et les défis techniques diffèrent. Pour un moteur de recherche pour entreprise, il faut être multi‐source, multiformat, et gérer la sécurité. En outre, il faut permettre d’administrer l’outil.</p>
<h4 id="toc-dans-la-version-libre-on-peut-côtéadministration">Dans la version libre, on peut, côté administration :</h4>
<ol>
<li>administrer les connecteurs aux sources de données vers de nombreuses sources (nous utilisons Apache ManifoldCF avec tous ses connecteurs) dont Sharepoint, Documentum, Alfresco et les partages de fichiers ;</li>
<li>gérer l’algorithme de pertinence qui classe les documents pour leur affichage suite à une requête ;</li>
<li>mettre en avant des documents pour des requêtes identifiées ;</li>
<li>créer des utilisateurs et leur assigner des rôles ;</li>
<li>voir des statistiques d’usage de l’outil ;</li>
<li>créer l’équivalent de Google AdWords (appelés promoliens) ;</li>
<li>gérer des synonymes ;</li>
<li>plein d’autres choses accessibles depuis la documentation confluence.</li>
</ol>
<h4 id="toc-et-côté-utilisateur-dans-la-version-libre-on-peut">Et côté utilisateur, dans la version libre, on peut :</h4>
<ol>
<li>chercher de façon simple ou avancée ;</li>
<li>prévisualiser les résultats ;</li>
<li>bénéficier de la correction orthographique et de l’auto‐complétion ;</li>
<li>utiliser des facettes pour filtrer les résultats ;</li>
<li>mettre des résultats dans un panier de favoris ;</li>
<li>créer des alertes par courriel quand des documents modifiés ou nouveaux correspondent à une requête.</li>
</ol>
<h2 id="toc-des-commentaires">Des commentaires ?</h2>
<p>Nous sommes en permanence à l’écoute des commentaires et suggestions pour faire avancer le produit, alors profitez‐en, que ce soit d’un point de vue technique ou fonctionnel, ça nous intéresse. Ah, et si vous êtes déjà un utilisateur, n’hésitez pas à en parler sur le Web !</p>
<p>Et si vous êtes assez nombreux, on pourrait organiser un <em>workshop</em> technique pour vous initier aux joies de Datafari version Communautaire.</p>
</div><div><a href="https://linuxfr.org/news/sortie-de-datafari-4-3-moteur-de-recherche-open-source-pour-entreprise.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/117648/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/sortie-de-datafari-4-3-moteur-de-recherche-open-source-pour-entreprise#comments">ouvrir dans le navigateur</a>
</p>
Datafarian00Ysabeau 🧶 🧦NÿcoZeroHeureDavy DefaudclaudexPierre Jarillonhttps://linuxfr.org/nodes/117648/comments.atomtag:linuxfr.org,2005:News/392542019-05-25T18:38:04+02:002019-05-25T21:02:29+02:00WaveDromLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>La nouvelle est tombée cette semaine sur la liste de diffusion du projet. La version 2.1.0 de WaveDrom vient de sortir.<br>
<img src="//img.linuxfr.org/img/68747470733a2f2f7761766564726f6d2e636f6d2f696d616765732f6c6f676f2e737667/logo.svg" alt="Logo de WaveDrom" title="Source : https://wavedrom.com/images/logo.svg"><br>
WaveDrom est un « standard » JavaScript permettant de décrire des <a href="https://fr.wikipedia.org/wiki/Chronogramme_(diagramme)">chronogrammes</a> sous forme de texte. La bibliothèque JavaScript se chargeant de convertir cette description texte en image SVG.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/wavedrom/nhji-OMkkUE/0mr7sbe0AwAJ" hreflang="en" href="https://linuxfr.org/redirect/104187">Annonce de la sortie de WaveDrom 2.1.0</a></li><li>lien nᵒ 2 : <a title="https://github.com/wavedrom/wavedrom" hreflang="en" href="https://linuxfr.org/redirect/104188">Le dépot officiel de WaveDrom</a></li><li>lien nᵒ 3 : <a title="https://github.com/wavedrom/wavedrom.github.io/releases/tag/v2.1.0" hreflang="en" href="https://linuxfr.org/redirect/104199">La publication 2.1.0</a></li><li>lien nᵒ 4 : <a title="https://wavedrom.com/" hreflang="en" href="https://linuxfr.org/redirect/104209">Site officiel</a></li></ul><div><p>L’idée de base est de pouvoir forger de beaux chronogrammes de qualité pour ses publications, mais en les décrivant en texte à la manière de Markdown ou LaTeX (en plus simple tout de même). Le texte suivant par exemple donnera l’image ci‐dessous :</p>
<pre><code class="html"> <span class="p"><</span><span class="nt">script</span> <span class="na">type</span><span class="o">=</span><span class="s">"WaveDrom"</span><span class="p">></span>
<span class="p">{</span> <span class="nx">signal</span> <span class="o">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"clk"</span><span class="p">,</span> <span class="nx">wave</span><span class="o">:</span> <span class="s2">"p......"</span> <span class="p">},</span>
<span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"bus"</span><span class="p">,</span> <span class="nx">wave</span><span class="o">:</span> <span class="s2">"x.34.5x"</span><span class="p">,</span> <span class="nx">data</span><span class="o">:</span> <span class="s2">"head body tail"</span> <span class="p">},</span>
<span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">"wire"</span><span class="p">,</span> <span class="nx">wave</span><span class="o">:</span> <span class="s2">"0.1..0."</span> <span class="p">},</span>
<span class="p">]}</span>
<span class="p"></</span><span class="nt">script</span><span class="p">></span></code></pre>
<p><img src="//img.linuxfr.org/img/687474703a2f2f7777772e66616269656e6d2e65752f696d616765732f7761766564726f6d5f73616d706c652e706e67/wavedrom_sample.png" alt="Échantillon Wavedrom" title="Source : http://www.fabienm.eu/images/wavedrom_sample.png"></p>
<p>WaveDrom a été enrichi de nouvelles fonctionnalités permettant notamment de faire des schémas électroniques simples. Mais aussi et surtout de documenter les bits d’un registre comme on peut le lire habituellement dans les fiches techniques (<em>datasheets</em>).</p>
<pre><code class="javascript"> <span class="p">{</span><span class="nx">reg</span><span class="o">:</span><span class="p">[</span>
<span class="p">{</span><span class="nx">name</span><span class="o">:</span> <span class="s1">'OP-32'</span><span class="p">,</span> <span class="nx">bits</span><span class="o">:</span> <span class="mi">7</span><span class="p">,</span> <span class="nx">attr</span><span class="o">:</span> <span class="mb">0b0111011</span><span class="p">},</span>
<span class="p">{</span><span class="nx">name</span><span class="o">:</span> <span class="s1">'rd'</span><span class="p">,</span> <span class="nx">bits</span><span class="o">:</span> <span class="mi">5</span><span class="p">,</span> <span class="nx">attr</span><span class="o">:</span> <span class="mi">0</span><span class="p">},</span>
<span class="p">{</span><span class="nx">name</span><span class="o">:</span> <span class="s1">'func3'</span><span class="p">,</span> <span class="nx">bits</span><span class="o">:</span> <span class="mi">3</span><span class="p">,</span> <span class="nx">attr</span><span class="o">:</span> <span class="p">[</span><span class="s1">'ADDW'</span><span class="p">,</span> <span class="s1">'SLLW'</span><span class="p">,</span> <span class="s1">'SRLW'</span><span class="p">,</span> <span class="s1">'SUBW'</span><span class="p">,</span> <span class="s1">'SRAW'</span><span class="p">]},</span>
<span class="p">{</span><span class="nx">bits</span><span class="o">:</span> <span class="mi">10</span><span class="p">},</span>
<span class="p">{</span><span class="nx">name</span><span class="o">:</span> <span class="s1">'func7'</span><span class="p">,</span> <span class="nx">bits</span><span class="o">:</span> <span class="mi">7</span><span class="p">,</span> <span class="nx">attr</span><span class="o">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">32</span><span class="p">]}</span>
<span class="p">]}</span></code></pre>
<p><img src="//img.linuxfr.org/img/687474703a2f2f7777772e66616269656e6d2e65752f696d616765732f7761766564726f6d5f7265675f72697363762e706e67/wavedrom_reg_riscv.png" alt="Registres RISC-V WaveDrom" title="Source : http://www.fabienm.eu/images/wavedrom_reg_riscv.png"></p>
<p>WaveDrom est d’abord une bibliothèque JavaScript pour le Web, mais il existe également un logiciel pour Windows, GNU/Linux et autres permettant de faire le rendu en local. Il existe également un greffon pour MediaWiki ou WordPress.</p>
<p>Cette nouvelle version majeure montre que le logiciel se porte bien et qu’il évolue toujours. On attend avec impatience le greffon pour LibreOffice qui serait très pratique pour faire des fiches techniques.</p>
</div><div><a href="https://linuxfr.org/news/wavedrom.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/117275/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/wavedrom#comments">ouvrir dans le navigateur</a>
</p>
martoniZeroHeureDavy Defaudpalm123Ysabeau 🧶 🧦Benoît SibaudM5oulFlorent Zarahttps://linuxfr.org/nodes/117275/comments.atomtag:linuxfr.org,2005:News/392142019-05-08T16:29:44+02:002019-05-08T16:32:55+02:00DeepDetect et LiveDetect pour les réseaux de neurones du serveur au Raspberry Pi 3Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p><a href="https://www.deepdetect.com/">DeepDetect</a> est un logiciel libre développé par <a href="https://jolibrain.com/">JoliBrain</a>, dont la vocation est de rendre accessibles et utilisables les innovations récentes de l’<a href="https://fr.wikipedia.org/wiki/Apprentissage_profond">apprentissage profond</a> (<em>deep learning</em>) et de permettre de les intégrer au sein d’applications. DeepDetect est constitué de deux logiciels libres :</p>
<ul>
<li>un <a href="https://github.com/jolibrain/deepdetect/">serveur</a> écrit en C++11 avec une API REST, permettant l’accès aux librairies sous‐jacentes Caffe, Caffe2, Tensorflow, Dlib, NCNN, etc. ;</li>
<li>une <a href="https://www.deepdetect.com/platform/">plate‐forme Web</a> permettant d’entraîner, d’organiser et d’utiliser ses modèles comme des petits bouts de code.</li>
</ul>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f7777772e646565706465746563742e636f6d2f696d672f706c6174666f726d2f707265646963745f696d6167652e706e67/predict_image.png" alt="DeepDetect Platform" title="Source : https://www.deepdetect.com/img/platform/predict_image.png"></p>
<p><em>La seconde partie en explique l’intérêt et présente un tutoriel d’installation sur Raspberry Pi.</em></p>
</div><ul><li>lien nᵒ 1 : <a title="https://www.deepdetect.com/" hreflang="en" href="https://linuxfr.org/redirect/104057">DeepDetect</a></li><li>lien nᵒ 2 : <a title="https://github.com/jolibrain/deepdetect" hreflang="en" href="https://linuxfr.org/redirect/104058">DeepDetect server</a></li><li>lien nᵒ 3 : <a title="https://www.deepdetect.com/platform" hreflang="en" href="https://linuxfr.org/redirect/104059">DeepDetect platform</a></li><li>lien nᵒ 4 : <a title="https://www.deepdetect.com/server/docs/embedded/" hreflang="en" href="https://linuxfr.org/redirect/104060">Tutoriel pour Raspberry Pi 3</a></li><li>lien nᵒ 5 : <a title="https://github.com/jolibrain/livedetect" hreflang="en" href="https://linuxfr.org/redirect/104061">LiveDetect</a></li></ul><div><p><a href="https://www.deepdetect.com/server/docs/embedded/">Traitement de la vidéo via des réseaux de neurones sur Raspberry Pi 3</a> avec <a href="https://www.deepdetect.com">DeepDetect</a> et <a href="https://github.com/jolibrain/livedetect">LiveDetect</a>.</p>
<h2 id="toc-deep-learning-et-embarqué">Deep Learning et embarqué</h2>
<p>Les avancées récentes de l’intelligence artificielle (ou, moins pompeusement, de l’automatisation avancée) sont en grande partie l’application de l’apprentissage profond (<em>Deep Learning</em>), permettant le traitement automatique du signal brut, ce qui restait difficile il y a une dizaine d’années. Dans ce contexte, il y a du sens à vouloir déplacer « l’intelligence » au plus près du capteur, pour un traitement local.</p>
<p>Rester au plus proche des capteurs permet aux applications Internet des objets (<em>Internet of Things</em> ou IoT) de réduire leur temps de traitement tout en améliorant la disponibilité de la bande passante et la construction d’applications locales fiables et sécurisées. </p>
<p>Il peut être contraignant de traiter des flux de données sur le « <em>cloud</em> » (informatique en nuage !) et ce pour différentes raisons :</p>
<ul>
<li>l’envoi de données du capteur en continu accapare une part importante de la bande passante, rendant la gestion de flotte de capteurs complexe et coûteuse ;</li>
<li>le manque de disponibilité de la bande passante provoque également un allongement de l’aller‐retour rendant plus difficile l’utilisation de réseaux de neurones pour des applications critiques ;</li>
<li>le traitement des flux de données à distance oblige à stocker des données parfois peu pertinentes et lourdes (vidéo 4K) et parfois sensibles ou privées.</li>
</ul>
<h2 id="toc-traitement-de-la-vidéo-avec-deepdetect-sur-un-raspberrypi3">Traitement de la vidéo avec DeepDetect sur un Raspberry Pi 3</h2>
<p>Le serveur DeepDetect est optimisé pour permettre d’obtenir des inférences depuis des serveurs avec processeurs central et graphique, jusqu’au Raspberry Pi 3, pour lequel il est optimisé via la bibliothèque <a href="https://github.com/tencent/ncnn">NCNN</a> <a href="https://github.com/jolibrain/ncnn">modifiée par Jolibrain</a>.</p>
<p>Ci‐dessous un <a href="https://github.com/jolibrain/livedetect/wiki/Step-by-step-for-Raspberry-Pi-3">tutoriel dans lequel nous installons DeepDetect pour Raspberry Pi avec un <em>back‐end</em> NCNN</a>, et nous utilisons <a href="https://github.com/jolibrain/livedetect/">LiveDetect</a>, un outil issu de <a href="https://www.deepdetect.com/ecoystem">l’écosystème DeepDetect</a> pour le traitement des flux vidéo. Cela nous permet de détecter des objets en temps réel et de les afficher.</p>
<p>Afin d’installer un serveur DeepDetect sur la Raspberry Pi, nous utilisons Docker pour la simplicité et les bonnes performances.</p>
<h3 id="toc-démarrer-le-conteneur-docker-deepdetect-pour-raspberrypi3">Démarrer le conteneur Docker DeepDetect pour Raspberry Pi 3</h3>
<pre><code>mkdir $HOME/models
docker pull jolibrain/deepdetect_ncnn_pi3
docker run -d -p 8080:8080 -v $HOME/models:/opt/models jolibrain/deepdetect_ncnn_pi3
</code></pre>
<h3 id="toc-détecter-des-objets-dans-une-vidéo">Détecter des objets dans une vidéo</h3>
<pre><code>wget https://github.com/jolibrain/livedetect/releases/download/1.0.1/livedetect-rpi3
./livedetect-rpi3 \
--port 8080 \
--host 127.0.0.1 \
--mllib ncnn \
--width 300 --height 300 \
--detection \
--create --repository /opt/models/voc/ \
--init "https://www.deepdetect.com/models/init/ncnn/squeezenet_ssd_voc_ncnn_300x300.tar.gz" \
--confidence 0.3 \
-v INFO \
-P "0.0.0.0:8888" \
--service voc \
--nclasses 21
</code></pre>
<p>Le rendu est alors disponible sur <code>http://localhost:8888</code> avec entre deux et trois trames vidéo par seconde (FPS).</p>
<p>Le <a href="https://github.com/jolibrain/livedetect/wiki/Step-by-step-for-Raspberry-Pi-3">tutoriel complet</a> pour Raspberry Pi 3 est disponible sur GitHub, ainsi que <a href="https://github.com/jolibrain/livedetect">les exemples avec LiveDetect</a>.</p>
<p>Vos retours nous intéressent ! Il n’est pas toujours simple de démarrer avec les réseaux de neurones, et nous souhaitons améliorer nos logiciels autant que possible.</p>
</div><div><a href="https://linuxfr.org/news/deepdetect-et-livedetect-pour-les-reseaux-de-neurones-du-serveur-au-raspberry-pi-3.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/117143/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/deepdetect-et-livedetect-pour-les-reseaux-de-neurones-du-serveur-au-raspberry-pi-3#comments">ouvrir dans le navigateur</a>
</p>
piloucheZeroHeureDavy DefaudBenoît Sibaudbubar🦥https://linuxfr.org/nodes/117143/comments.atomtag:linuxfr.org,2005:News/391952019-04-26T17:18:24+02:002019-04-26T19:15:55+02:00Sortie de gtk-fortran 19.04Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Lancée début 2011, la bibliothèque gtk-fortran permet de créer des interfaces graphiques <a href="https://fr.m.wikipedia.org/wiki/GTK_(bo%C3%AEte_%C3%A0_outils)">GTK</a> dans des programmes en Fortran. Elle est multi‐plate‐forme (GNU/Linux, Windows via MSYS2, BSD et macOS) et le projet est publié sous licence GNU GPL v3. Environ 10 000 fonctions sont accessibles (GTK, GDK, GdkPixbuf, Cairo, Pango, ATK, GLib, GObject, GIO). En plus de l’aspect interface graphique, gtk-fortran permet également :</p>
<ul>
<li>d’accéder aux fonctions de la GLib ;</li>
<li>d’accéder aux fonctionnalités de la bibliothèque de tracé scientifique PLplot.</li>
</ul>
<p>La version 19.04 vient de sortir. Une présentation technique de gtk-fortran et les nouveautés de cette version suivent en deuxième partie. Profitez‐en, avec cette dépêche, c’est l’auteur de la bibliothèque qui régale !</p>
</div><ul><li>lien nᵒ 1 : <a title="https://linuxfr.org/users/vmagnin/journaux/gtk-fortran-19-04-est-sorti-e" hreflang="fr" href="https://linuxfr.org/redirect/103981">Journal à l’origine de la dépêche</a></li><li>lien nᵒ 2 : <a title="https://github.com/vmagnin/gtk-fortran/wiki" hreflang="en" href="https://linuxfr.org/redirect/103982">Page principale du projet</a></li><li>lien nᵒ 3 : <a title="https://wg5-fortran.org/N1601-N1650/N1648.pdf" hreflang="en" href="https://linuxfr.org/redirect/103983">John Reid : The New Features of Fortran 2003 (PDF)</a></li><li>lien nᵒ 4 : <a title="https://egr.vcu.edu/departments/mechanical/research/nuclear-simulator-lab/" hreflang="en" href="https://linuxfr.org/redirect/103984">Un exemple de projet utilisant gtk-fortran</a></li></ul><div><h2 id="toc-quelques-aspects-techniques">Quelques aspects techniques</h2>
<p>Techniquement, gtk-fortran utilise le module ISO_C_BINDING introduit dans la norme Fortran 2003 afin de faciliter et de normaliser l’interfaçage entre les programmes Fortran et C, en assurant au mieux l’interopérabilité des types de données, des pointeurs, des appels de fonctions, etc.</p>
<p>Un programme Python se charge de parcourir les fichiers <code>.h</code> des bibliothèques GTK afin d’essayer d’en extraire la substantifique moelle à l’aide d’expressions régulières et de générer les interfaces Fortran permettant d’accéder aux fonctions C.</p>
<p>Par exemple, pour ce prototype C situé dans <code>/usr/include/gtk-3.0/gtk/gtkwindow.h</code> :</p>
<pre><code class="C"> <span class="kt">void</span> <span class="nf">gtk_window_set_title</span> <span class="p">(</span><span class="n">GtkWindow</span> <span class="o">*</span><span class="n">window</span><span class="p">,</span> <span class="k">const</span> <span class="n">gchar</span> <span class="o">*</span><span class="n">title</span><span class="p">);</span></code></pre>
<p>On obtient l’interface Fortran suivante :</p>
<pre><code class="Fortran"> <span class="k">subroutine </span><span class="n">gtk_window_set_title</span><span class="p">(</span><span class="n">window</span><span class="p">,</span> <span class="n">title</span><span class="p">)</span> <span class="k">bind</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>
<span class="k">use </span><span class="nb">iso_c_binding</span><span class="p">,</span> <span class="n">only</span><span class="p">:</span> <span class="kt">c_ptr</span><span class="p">,</span> <span class="kt">c_char</span>
<span class="kt"> </span><span class="k">type</span><span class="p">(</span><span class="kt">c_ptr</span><span class="p">),</span> <span class="k">value</span> <span class="kd">::</span> <span class="n">window</span>
<span class="kt">character</span><span class="p">(</span><span class="nb">kind</span><span class="o">=</span><span class="kt">c_char</span><span class="p">),</span> <span class="k">dimension</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="kd">::</span> <span class="n">title</span>
<span class="k">end subroutine</span></code></pre>
<p>La plupart des énumérations sont également traduites. Par exemple :</p>
<pre><code class="C"> <span class="k">typedef</span> <span class="k">enum</span>
<span class="p">{</span>
<span class="n">G_DATE_DAY</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
<span class="n">G_DATE_MONTH</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>
<span class="n">G_DATE_YEAR</span> <span class="o">=</span> <span class="mi">2</span>
<span class="p">}</span> <span class="n">GDateDMY</span><span class="p">;</span></code></pre>
<p>Ce qui devient en Fortran :</p>
<pre><code class="Fortran"> <span class="k">enum</span><span class="p">,</span> <span class="k">bind</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="c">!GDateDMY</span>
<span class="k">enumerator</span> <span class="kd">::</span> <span class="n">G_DATE_DAY</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">enumerator</span> <span class="kd">::</span> <span class="n">G_DATE_MONTH</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">enumerator</span> <span class="kd">::</span> <span class="n">G_DATE_YEAR</span> <span class="o">=</span> <span class="mi">2</span>
<span class="k">end enum</span></code></pre>
<h2 id="toc-nouveautés-dans-gtk-fortran1904">Nouveautés dans gtk-fortran 19.04</h2>
<p>La version 19.04 (la numérotation correspond à la version d’Ubuntu utilisée pour générer la bibliothèque) est basée sur GTK 3.24.8, GLib 2.60.0 et GTK 2.24.32. Elle se caractérise également par l’ajout d’outils permettant de repérer les fonctions GTK obsolètes (<em>deprecated</em>), la parallélisation de la compilation (<code>make -j</code>) dans la branche GTK 3, un effort particulier sur la qualité de la documentation, des corrections de bogues dans les exemples… </p>
<h2 id="toc-futurs-développements">Futurs développements</h2>
<p>Concernant l’avenir, le projet se prépare lentement à l’arrivée de GTK 4. Avec également une réflexion sur l’opportunité de passer de CMake à Meson, outil désormais utilisé par le projet GTK.</p>
<p>Les bonnes volontés sont bien sûr les bienvenues, d’autant plus que nous ne sommes pas légion !</p>
</div><div><a href="https://linuxfr.org/news/sortie-de-gtk-fortran-19-04.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/117061/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/sortie-de-gtk-fortran-19-04#comments">ouvrir dans le navigateur</a>
</p>
vmagninZeroHeureBenoît SibaudDavy DefaudNils RatusznikYsabeau 🧶 🧦palm123https://linuxfr.org/nodes/117061/comments.atomtag:linuxfr.org,2005:News/390972019-03-06T10:26:05+01:002019-03-07T19:10:02+01:00Développement Web « fullstack », application de dessin collaboratifLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Une application Web peut être implémentée selon différentes architectures mais comporte généralement une partie client et une partie serveur. De nombreux langages proposent des cadriciels pour implémenter la partie serveur. En revanche, pour la partie client, seuls les langages HTML, CSS et JavaScript sont gérés directement par les navigateurs. Certains outils permettent cependant d’écrire le code client dans un autre langage puis de « transpiler » vers du code JavaScript compréhensible par un navigateur. On peut alors coder toute une application (partie client et partie serveur) avec un seul langage, grâce à ce genre de cadriciel « <em>fullstack</em> ».</p>
<hr>
<p><em>N. D. M. : Cette dépêche détaille le développement d’une application Web permettant de faire du dessin collaboratif. Les codes client et serveur sont en JavaScript dans la première partie, puis en Haskell « isomorphique » dans la seconde et, enfin, en C++ « basé widgets ».</em></p>
</div><ul><li>lien nᵒ 1 : <a title="https://linuxfr.org/news/comparaison-de-technologies-web-pour-implementer-une-application-de-dessin-basique" hreflang="fr" href="https://linuxfr.org/redirect/103669">Comparaison de technologies Web pour implémenter une application de dessin basique</a></li><li>lien nᵒ 2 : <a title="https://linuxfr.org/users/nokomprendo-3" hreflang="fr" href="https://linuxfr.org/redirect/103670">Autres dépêches de l’auteur sur le Web en C++ et Haskell</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-en-javascript">En JavaScript</a><ul>
<li><a href="#toc-code-serveur">Code serveur</a></li>
<li><a href="#toc-code-client">Code client</a></li>
</ul>
</li>
<li>
<a href="#toc-en-haskell-isomorphique">En Haskell « isomorphique »</a><ul>
<li><a href="#toc-code-commun">Code commun</a></li>
<li><a href="#toc-code-client-1">Code client</a></li>
<li><a href="#toc-code-serveur-1">Code serveur</a></li>
</ul>
</li>
<li>
<a href="#toc-en-c-bas%C3%A9widgets">En C++ « basé widgets »</a><ul>
<li><a href="#toc-application-principale">Application principale</a></li>
<li><a href="#toc-contr%C3%B4leur">Contrôleur</a></li>
<li><a href="#toc-application-de-dessin">Application de dessin</a></li>
</ul>
</li>
<li><a href="#toc-conclusion">Conclusion</a></li>
</ul>
<p>Dans cet article, on considère un cadriciel C++ « basé <em>widget</em> » (Wt) et un cadriciel Haskell « isomorphique » (Miso/Servant). L’application réalisée permet de faire du dessin collaboratif : chaque utilisateur peut dessiner des chemins en cliquant et en déplaçant sa souris ; lorsqu’un chemin est terminé (le bouton de la souris est relâché), le chemin est envoyé au serveur qui le diffuse à tous les clients. Dans cette applications, la partie serveur gère les chemins et les connexions, et la partie client gère le dessin interactif.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6e6f6b6f6d7072656e646f2e6672616d612e696f2f7475746f5f666f6e6374696f6e6e656c2f706f7374732f7475746f5f666f6e6374696f6e6e656c5f33332f696d616765732f69736f7061696e742e676966/isopaint.gif" alt="dessin collaboratif" title="Source : https://nokomprendo.frama.io/tuto_fonctionnel/posts/tuto_fonctionnel_33/images/isopaint.gif"></p>
<p>[<a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint">code source</a>]</p>
<h2 id="toc-en-javascript">En JavaScript</h2>
<p>Avant de voir les cadriciels proposés, voyons comment implémenter l’application en JavaScript, de façon simple.</p>
<h3 id="toc-code-serveur">Code serveur</h3>
<p>Pour le serveur (<a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint/isopaint_js/src/app.js"><code>src/app.js</code></a>), on dispose d’outils très classiques : Node.js, Express.js, Socket.IO. Node.js permet d’implémenter le serveur web de base, qui contient l’ensemble des chemins dessinés. Express.js permet d’implémenter le routage d’URL, c'est‐à‐dire ici la route racine « / », qui envoie au client les fichiers statiques (du dossier « static » de la machine serveur). Enfin, Socket.IO permet de diffuser des messages aux clients connectés :</p>
<ul>
<li>à la connection d’un client, le serveur envoie un message « <em>stoh all paths</em> » contenant tous les chemins du dessin actuel ;</li>
<li>lorsqu’un client est connecté, il peut envoyer un chemin au serveur, via un message « <em>htos new path</em> » ;</li>
<li>lorsqu’un client envoie un chemin, le serveur réagit en stockant le nouveau chemin et en le rediffusant à tous les clients, via un message « <em>stoh new path</em> ».</li>
</ul>
<pre><code class="javascript"><span class="s2">"use strict"</span><span class="p">;</span>
<span class="kr">const</span> <span class="nx">port</span> <span class="o">=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="kr">const</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"express"</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="kr">const</span> <span class="nx">http</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"http"</span><span class="p">).</span><span class="nx">Server</span><span class="p">(</span><span class="nx">app</span><span class="p">);</span>
<span class="kr">const</span> <span class="nx">io</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">"socket.io"</span><span class="p">)(</span><span class="nx">http</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">paths</span> <span class="o">=</span> <span class="p">[];</span>
<span class="c1">// serve static files</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="s2">"/"</span><span class="p">,</span> <span class="nx">express</span><span class="p">.</span><span class="kr">static</span><span class="p">(</span><span class="s2">"./static"</span><span class="p">));</span>
<span class="c1">// client connection</span>
<span class="nx">io</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">"connection"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">socket</span><span class="p">){</span>
<span class="c1">// when a new connection is accepted, send all paths</span>
<span class="nx">socket</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="s2">"stoh all paths"</span><span class="p">,</span> <span class="nx">paths</span><span class="p">);</span>
<span class="c1">// when a client sends a new path</span>
<span class="nx">socket</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">"htos new path"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">path</span><span class="p">){</span>
<span class="c1">// store the new path</span>
<span class="nx">paths</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">path</span><span class="p">);</span>
<span class="c1">// send the new path to all clients</span>
<span class="nx">io</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="s2">"stoh new path"</span><span class="p">,</span> <span class="nx">path</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">http</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">port</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Listening on port </span><span class="si">${</span><span class="nx">port</span><span class="si">}</span><span class="sb">...`</span><span class="p">);</span>
<span class="p">});</span></code></pre>
<h3 id="toc-code-client">Code client</h3>
<p>Pour le client (<a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint/isopaint_js/static/index.html"><code>static/index.html</code></a>), on définit un canevas de dessin et quelques fonctions auxiliaires pour récupérer la position de la souris, tracer un chemin, etc.</p>
<pre><code class="javascript"> <span class="o"><</span><span class="nx">canvas</span> <span class="nx">id</span><span class="o">=</span><span class="s2">"canvas_draw"</span> <span class="nx">width</span><span class="o">=</span><span class="s2">"400"</span> <span class="nx">height</span><span class="o">=</span><span class="s2">"300"</span>
<span class="nx">style</span><span class="o">=</span><span class="s2">"border:1px solid black"</span><span class="o">></span> <span class="o"><</span><span class="err">/canvas></span>
<span class="o"><</span><span class="nx">script</span><span class="o">></span>
<span class="kd">function</span> <span class="nx">getXY</span><span class="p">(</span><span class="nx">canvas</span><span class="p">,</span> <span class="nx">evt</span><span class="p">)</span> <span class="p">{</span>
<span class="kr">const</span> <span class="nx">rect</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">getBoundingClientRect</span><span class="p">();</span>
<span class="kr">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="nx">evt</span><span class="p">.</span><span class="nx">clientX</span> <span class="o">-</span> <span class="nx">rect</span><span class="p">.</span><span class="nx">left</span><span class="p">;</span>
<span class="kr">const</span> <span class="nx">y</span> <span class="o">=</span> <span class="nx">evt</span><span class="p">.</span><span class="nx">clientY</span> <span class="o">-</span> <span class="nx">rect</span><span class="p">.</span><span class="nx">top</span><span class="p">;</span>
<span class="k">return</span> <span class="p">{</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">};</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">setDrawingStyle</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="s1">'black'</span><span class="p">;</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">lineWidth</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">lineCap</span> <span class="o">=</span> <span class="s2">"round"</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">drawPath</span><span class="p">(</span><span class="nx">canvas</span><span class="p">,</span> <span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">length</span> <span class="o">>=</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
<span class="kr">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="nx">canvas_draw</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s2">"2d"</span><span class="p">);</span>
<span class="nx">setDrawingStyle</span><span class="p">(</span><span class="nx">ctx</span><span class="p">);</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">beginPath</span><span class="p">();</span>
<span class="p">[</span><span class="nx">p0</span><span class="p">,</span> <span class="p">...</span><span class="nx">ps</span><span class="p">]</span> <span class="o">=</span> <span class="nx">path</span><span class="p">;</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">moveTo</span><span class="p">(</span><span class="nx">p0</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> <span class="nx">p0</span><span class="p">.</span><span class="nx">y</span><span class="p">);</span>
<span class="nx">ps</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">p</span> <span class="p">=></span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">lineTo</span><span class="p">(</span><span class="nx">p</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> <span class="nx">p</span><span class="p">.</span><span class="nx">y</span><span class="p">));</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">stroke</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="o"><</span><span class="err">/script></span></code></pre>
<p>On utilise également Socket.IO pour gérer les communications avec le serveur : récupérer les chemins initiaux, récupérer les nouveaux chemins successifs. Enfin, on gère les événements utilisateur de façon habituelle et on propage les données correspondantes au serveur : créer et mettre à jour un chemin courant lorsqu’on appuie et déplace la souris, envoyer le chemin lorsqu’on relâche :</p>
<pre><code class="javascript"> <span class="o"><</span><span class="nx">script</span> <span class="nx">src</span><span class="o">=</span><span class="s2">"/socket.io-2.2.0.js"</span><span class="o">><</span><span class="err">/script></span>
<span class="o"><</span><span class="nx">script</span><span class="o">></span>
<span class="kr">const</span> <span class="nx">socket</span> <span class="o">=</span> <span class="nx">io</span><span class="p">();</span>
<span class="c1">// when the server sends all paths</span>
<span class="nx">socket</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">"stoh all paths"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">paths</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">paths</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">path</span> <span class="p">=></span> <span class="nx">drawPath</span><span class="p">(</span><span class="nx">canvas_draw</span><span class="p">,</span> <span class="nx">path</span><span class="p">));</span>
<span class="p">});</span>
<span class="c1">// when the server sends a new path</span>
<span class="nx">socket</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s2">"stoh new path"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">drawPath</span><span class="p">(</span><span class="nx">canvas_draw</span><span class="p">,</span> <span class="nx">path</span><span class="p">);</span>
<span class="p">});</span>
<span class="kd">let</span> <span class="nx">current_path</span> <span class="o">=</span> <span class="p">[];</span>
<span class="c1">// when the user begins to draw a path</span>
<span class="nx">canvas_draw</span><span class="p">.</span><span class="nx">onmousedown</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">evt0</span><span class="p">)</span> <span class="p">{</span>
<span class="kr">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="nx">canvas_draw</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s2">"2d"</span><span class="p">);</span>
<span class="nx">setDrawingStyle</span><span class="p">(</span><span class="nx">ctx</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">p0</span> <span class="o">=</span> <span class="nx">getXY</span><span class="p">(</span><span class="nx">canvas_draw</span><span class="p">,</span> <span class="nx">evt0</span><span class="p">);</span>
<span class="nx">current_path</span> <span class="o">=</span> <span class="p">[</span><span class="nx">p0</span><span class="p">];</span>
<span class="c1">// set mousemove callback</span>
<span class="nx">canvas_draw</span><span class="p">.</span><span class="nx">onmousemove</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">evt1</span><span class="p">)</span> <span class="p">{</span>
<span class="kr">const</span> <span class="nx">p1</span> <span class="o">=</span> <span class="nx">getXY</span><span class="p">(</span><span class="nx">canvas_draw</span><span class="p">,</span> <span class="nx">evt1</span><span class="p">);</span>
<span class="nx">current_path</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">p1</span><span class="p">);</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">beginPath</span><span class="p">();</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">moveTo</span><span class="p">(</span><span class="nx">p0</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> <span class="nx">p0</span><span class="p">.</span><span class="nx">y</span><span class="p">);</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">lineTo</span><span class="p">(</span><span class="nx">p1</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> <span class="nx">p1</span><span class="p">.</span><span class="nx">y</span><span class="p">);</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">stroke</span><span class="p">();</span>
<span class="nx">p0</span> <span class="o">=</span> <span class="nx">p1</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="c1">// when the user finishes to draw a path</span>
<span class="nx">canvas_draw</span><span class="p">.</span><span class="nx">onmouseup</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">evt</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// unset mousemove callback</span>
<span class="nx">canvas_draw</span><span class="p">.</span><span class="nx">onmousemove</span> <span class="o">=</span> <span class="p">{};</span>
<span class="c1">// send the path to the server</span>
<span class="nx">socket</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="s2">"htos new path"</span><span class="p">,</span> <span class="nx">current_path</span><span class="p">);</span>
<span class="p">};</span>
<span class="o"><</span><span class="err">/script></span></code></pre>
<p>Le code JavaScript complet est plutôt clair et concis ; les fonctions pour dessiner dans un canevas et la bibliothèque Socket.IO sont particulièrement simples. On notera cependant que les données manipulées dans cette application, ainsi que les fonctionnalités implémentées, sont très limitées. Sur une application plus réaliste, on utiliserait plutôt une vraie architecture de code (par exemple MVC, Flux…) et des bibliothèques dédiées (React, Vue.js, etc.).</p>
<h2 id="toc-en-haskell-isomorphique">En Haskell « isomorphique »</h2>
<p>Une application Web isomorphique est une application dont le code s’exécute à la fois côté client et côté serveur. Il s’agit généralement d’une application mono‐page, c’est‐à‐dire avec un code client assez lourd, mais dont le premier rendu est réalisé par le serveur. Ceci permet de fournir une première vue à l’utilisateur avant que l’application soit complètement chargée dans le navigateur.</p>
<p>Le langage JavaScript est souvent utilisé pour implémenter ce genre d’application car il peut directement s’exécuter dans un navigateur. Cependant, il existe également des outils permettant d’écrire le code client dans d’autres langages et de le « transpiler » ensuite vers JavaScript.</p>
<p>En Haskell, les bibliothèques <a href="https://haskell-miso.org/">Miso</a> et <a href="https://www.servant.dev/">Servant</a> permettent d’implémenter des applications Web isomorphiques avec une architecture de type Flux. Pour cela, on définit le modèle des données, les actions possibles et les fonctions pour calculer une vue et pour gérer les actions. Ces éléments sont ensuite utilisés automatiquement et de façon asynchrone dans l’application du client. Ils peuvent également être utilisés pour la partie serveur.</p>
<h3 id="toc-code-commun">Code commun</h3>
<p>Dans le code commun au client et au serveur (<a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint/isopaint_miso/src/Common.hs"><code>src/Common.hs</code></a>), on définit le modèle (données manipulées par l’application), la fonction de rendu (qui calcule la vue d’un modèle) et les actions (que peut générer la vue).</p>
<pre><code class="haskell"><span class="c1">-- model</span>
<span class="kr">type</span> <span class="kt">Path</span> <span class="ow">=</span> <span class="p">[(</span><span class="kt">Double</span><span class="p">,</span> <span class="kt">Double</span><span class="p">)]</span>
<span class="kr">data</span> <span class="kt">Model</span> <span class="ow">=</span> <span class="kt">Model</span>
<span class="p">{</span> <span class="n">allPaths_</span> <span class="ow">::</span> <span class="p">[</span><span class="kt">Path</span><span class="p">]</span> <span class="c1">-- all the paths, sent by the server</span>
<span class="p">,</span> <span class="n">currentPath_</span> <span class="ow">::</span> <span class="kt">Path</span> <span class="c1">-- current path (when the user is drawing)</span>
<span class="p">,</span> <span class="n">currentXy_</span> <span class="ow">::</span> <span class="p">(</span><span class="kt">Double</span><span class="p">,</span> <span class="kt">Double</span><span class="p">)</span> <span class="c1">-- last position of the mouse (when drawing)</span>
<span class="p">,</span> <span class="n">drawing_</span> <span class="ow">::</span> <span class="kt">Bool</span> <span class="c1">-- set whether the user is drawing or not</span>
<span class="p">}</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Eq</span><span class="p">,</span> <span class="kt">Show</span><span class="p">)</span>
<span class="nf">initialModel</span> <span class="ow">::</span> <span class="kt">Model</span>
<span class="nf">initialModel</span> <span class="ow">=</span> <span class="kt">Model</span> <span class="kt">[]</span> <span class="kt">[]</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> <span class="kt">False</span>
<span class="c1">-- view</span>
<span class="nf">homeView</span> <span class="ow">::</span> <span class="kt">Model</span> <span class="ow">-></span> <span class="kt">View</span> <span class="kt">Action</span>
<span class="nf">homeView</span> <span class="kr">_</span> <span class="ow">=</span> <span class="n">div_</span>
<span class="kt">[]</span>
<span class="p">[</span> <span class="n">p_</span> <span class="kt">[]</span> <span class="p">[</span> <span class="n">text</span> <span class="s">"isopaint_miso"</span> <span class="p">]</span>
<span class="p">,</span> <span class="n">canvas_</span>
<span class="p">[</span> <span class="n">id_</span> <span class="s">"canvas_draw"</span> <span class="p">,</span> <span class="n">width_</span> <span class="s">"400"</span> <span class="p">,</span> <span class="n">height_</span> <span class="s">"300"</span>
<span class="p">,</span> <span class="n">style_</span> <span class="p">(</span><span class="n">singleton</span> <span class="s">"border"</span> <span class="s">"1px solid black"</span><span class="p">)</span>
<span class="p">,</span> <span class="n">onMouseDown</span> <span class="kt">MouseDown</span> <span class="c1">-- when mouse down, generate a MouseDown action</span>
<span class="p">,</span> <span class="n">onMouseUp</span> <span class="kt">MouseUp</span> <span class="c1">-- when mouse up, generate a MouseUp action</span>
<span class="p">]</span>
<span class="kt">[]</span>
<span class="p">]</span>
<span class="c1">-- actions</span>
<span class="kr">data</span> <span class="kt">Action</span>
<span class="ow">=</span> <span class="kt">NoOp</span>
<span class="o">|</span> <span class="kt">RedrawCanvas</span>
<span class="o">|</span> <span class="kt">MouseDown</span>
<span class="o">|</span> <span class="kt">MouseUp</span>
<span class="o">|</span> <span class="kt">MouseMove</span> <span class="p">(</span><span class="kt">Int</span><span class="p">,</span> <span class="kt">Int</span><span class="p">)</span>
<span class="o">|</span> <span class="kt">SetXy</span> <span class="p">(</span><span class="kt">Double</span><span class="p">,</span> <span class="kt">Double</span><span class="p">)</span>
<span class="o">|</span> <span class="kt">SetPaths</span> <span class="p">[</span><span class="kt">Path</span><span class="p">]</span>
<span class="o">|</span> <span class="kt">SendXhr</span> <span class="kt">Path</span>
<span class="o">|</span> <span class="kt">RecvSse</span> <span class="p">(</span><span class="kt">Maybe</span> <span class="kt">Path</span><span class="p">)</span>
<span class="o">|</span> <span class="kt">InitAllPaths</span>
<span class="kr">deriving</span> <span class="p">(</span><span class="kt">Eq</span><span class="p">,</span> <span class="kt">Show</span><span class="p">)</span></code></pre>
<h3 id="toc-code-client-1">Code client</h3>
<p>Dans le code spécifique au client (<a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint/isopaint_miso/src/client.hs"><code>src/client.hs</code></a>), on définit la fonction de mise à jour du modèle en fonction des actions demandées (requêtes <a href="https://fr.wikipedia.org/wiki/Ajax_(informatique)">AJAX</a> au serveur, dessin interactif, redessin complet, etc.).</p>
<pre><code class="haskell"><span class="nf">updateModel</span> <span class="ow">::</span> <span class="kt">Action</span> <span class="ow">-></span> <span class="kt">Model</span> <span class="ow">-></span> <span class="kt">Effect</span> <span class="kt">Action</span> <span class="kt">Model</span>
<span class="c1">-- nothing to do</span>
<span class="nf">updateModel</span> <span class="kt">NoOp</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">noEff</span> <span class="n">m</span>
<span class="c1">-- mouse down: begin drawing a path</span>
<span class="nf">updateModel</span> <span class="kt">MouseDown</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">noEff</span> <span class="n">m</span> <span class="p">{</span> <span class="n">currentPath_</span> <span class="ow">=</span> <span class="kt">[]</span><span class="p">,</span> <span class="n">drawing_</span> <span class="ow">=</span> <span class="kt">True</span> <span class="p">}</span>
<span class="c1">-- mouse move: get position and ask to update the model using a SetXy action</span>
<span class="nf">updateModel</span> <span class="p">(</span><span class="kt">MouseMove</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">))</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">m</span> <span class="o"><#</span>
<span class="kr">if</span> <span class="n">drawing_</span> <span class="n">m</span>
<span class="kr">then</span> <span class="kr">do</span>
<span class="n">left</span> <span class="ow"><-</span> <span class="n">jsRectLeft</span>
<span class="n">top</span> <span class="ow"><-</span> <span class="n">jsRectTop</span>
<span class="kr">let</span> <span class="n">x'</span> <span class="ow">=</span> <span class="n">fromIntegral</span> <span class="o">$</span> <span class="n">x</span> <span class="o">-</span> <span class="n">left</span>
<span class="kr">let</span> <span class="n">y'</span> <span class="ow">=</span> <span class="n">fromIntegral</span> <span class="o">$</span> <span class="n">y</span> <span class="o">-</span> <span class="n">top</span>
<span class="n">pure</span> <span class="o">$</span> <span class="kt">SetXy</span> <span class="p">(</span><span class="n">x'</span><span class="p">,</span> <span class="n">y'</span><span class="p">)</span>
<span class="kr">else</span> <span class="n">pure</span> <span class="kt">NoOp</span>
<span class="c1">-- update position and ask to redraw the canvas using a RedrawCanvas action</span>
<span class="nf">updateModel</span> <span class="p">(</span><span class="kt">SetXy</span> <span class="n">xy</span><span class="p">)</span> <span class="n">m</span> <span class="ow">=</span>
<span class="n">m</span> <span class="p">{</span> <span class="n">currentPath_</span> <span class="ow">=</span> <span class="n">xy</span> <span class="kt">:</span> <span class="n">currentPath_</span> <span class="n">m</span> <span class="p">}</span> <span class="o"><#</span> <span class="n">pure</span> <span class="kt">RedrawCanvas</span>
<span class="c1">-- mouse up: finish drawing the current path (send the path to the server)</span>
<span class="nf">updateModel</span> <span class="kt">MouseUp</span> <span class="p">(</span><span class="kt">Model</span> <span class="n">a</span> <span class="n">c</span> <span class="n">xy</span> <span class="kr">_</span><span class="p">)</span> <span class="ow">=</span> <span class="kt">Model</span> <span class="n">a</span> <span class="kt">[]</span> <span class="n">xy</span> <span class="kt">False</span> <span class="o"><#</span> <span class="n">pure</span> <span class="p">(</span><span class="kt">SendXhr</span> <span class="n">c</span><span class="p">)</span>
<span class="c1">-- send a path to the server</span>
<span class="nf">updateModel</span> <span class="p">(</span><span class="kt">SendXhr</span> <span class="n">path</span><span class="p">)</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">m</span> <span class="o"><#</span> <span class="p">(</span><span class="n">xhrPath</span> <span class="n">path</span> <span class="o">>></span> <span class="n">pure</span> <span class="kt">NoOp</span><span class="p">)</span>
<span class="c1">-- register to Server-Sent Event, for receiving new paths from other clients</span>
<span class="nf">updateModel</span> <span class="p">(</span><span class="kt">RecvSse</span> <span class="kt">Nothing</span><span class="p">)</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">noEff</span> <span class="n">m</span>
<span class="nf">updateModel</span> <span class="p">(</span><span class="kt">RecvSse</span> <span class="p">(</span><span class="kt">Just</span> <span class="n">path</span><span class="p">))</span> <span class="n">m</span> <span class="ow">=</span>
<span class="n">m</span> <span class="p">{</span> <span class="n">allPaths_</span> <span class="ow">=</span> <span class="n">path</span> <span class="kt">:</span> <span class="n">allPaths_</span> <span class="n">m</span> <span class="p">}</span> <span class="o"><#</span> <span class="n">pure</span> <span class="kt">RedrawCanvas</span>
<span class="c1">-- clear the canvas and redraw the paths</span>
<span class="nf">updateModel</span> <span class="kt">RedrawCanvas</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">m</span> <span class="o"><#</span> <span class="kr">do</span>
<span class="n">w</span> <span class="ow"><-</span> <span class="n">jsWidth</span>
<span class="n">h</span> <span class="ow"><-</span> <span class="n">jsHeight</span>
<span class="n">ctx</span> <span class="ow"><-</span> <span class="n">jsCtx</span>
<span class="n">clearRect</span> <span class="mi">0</span> <span class="mi">0</span> <span class="n">w</span> <span class="n">h</span> <span class="n">ctx</span>
<span class="n">lineCap</span> <span class="kt">LineCapRound</span> <span class="n">ctx</span>
<span class="n">mapM_</span> <span class="p">(</span><span class="n">drawPath</span> <span class="n">ctx</span><span class="p">)</span> <span class="o">$</span> <span class="n">allPaths_</span> <span class="n">m</span>
<span class="n">drawPath</span> <span class="n">ctx</span> <span class="o">$</span> <span class="n">currentPath_</span> <span class="n">m</span>
<span class="n">pure</span> <span class="kt">NoOp</span>
<span class="c1">-- initialize paths: ask all paths to the server then update using a SetPaths action</span>
<span class="nf">updateModel</span> <span class="kt">InitAllPaths</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">m</span> <span class="o"><#</span> <span class="kr">do</span> <span class="kt">SetPaths</span> <span class="o"><$></span> <span class="n">xhrAllPaths</span>
<span class="c1">-- update paths then ask to redraw the canvas</span>
<span class="nf">updateModel</span> <span class="p">(</span><span class="kt">SetPaths</span> <span class="n">paths</span><span class="p">)</span> <span class="n">m</span> <span class="ow">=</span> <span class="n">m</span> <span class="p">{</span> <span class="n">allPaths_</span> <span class="ow">=</span> <span class="n">paths</span> <span class="p">}</span> <span class="o"><#</span> <span class="n">pure</span> <span class="kt">RedrawCanvas</span></code></pre>
<p>On définit également quelques fonctions auxiliaires pour implémenter les requêtes AJAX au serveur, et le dessin dans le canevas :</p>
<pre><code class="haskell"><span class="c1">-- send a new path to the server ("/xhrPath" endpoint)</span>
<span class="nf">xhrPath</span> <span class="ow">::</span> <span class="kt">Path</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">xhrPath</span> <span class="n">path</span> <span class="ow">=</span> <span class="n">void</span> <span class="o">$</span> <span class="n">xhrByteString</span> <span class="o">$</span> <span class="kt">Request</span> <span class="kt">POST</span> <span class="s">"/xhrPath"</span> <span class="kt">Nothing</span> <span class="n">hdr</span> <span class="kt">False</span> <span class="n">dat</span>
<span class="kr">where</span> <span class="n">hdr</span> <span class="ow">=</span> <span class="p">[(</span><span class="s">"Content-type"</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">)]</span>
<span class="n">dat</span> <span class="ow">=</span> <span class="kt">StringData</span> <span class="o">$</span> <span class="n">toMisoString</span> <span class="o">$</span> <span class="n">encode</span> <span class="n">path</span>
<span class="c1">-- ask for the paths ("/xhrPath" endpoint)</span>
<span class="nf">xhrAllPaths</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="p">[</span><span class="kt">Path</span><span class="p">]</span>
<span class="nf">xhrAllPaths</span> <span class="ow">=</span> <span class="n">fromMaybe</span> <span class="kt">[]</span> <span class="o">.</span> <span class="n">decodeStrict</span> <span class="o">.</span> <span class="n">fromJust</span> <span class="o">.</span> <span class="n">contents</span> <span class="o"><$></span>
<span class="n">xhrByteString</span> <span class="p">(</span><span class="kt">Request</span> <span class="kt">GET</span> <span class="s">"/api"</span> <span class="kt">Nothing</span> <span class="kt">[]</span> <span class="kt">False</span> <span class="kt">NoData</span><span class="p">)</span>
<span class="c1">-- handle a Server-Sent Event by generating a RecvSse action</span>
<span class="nf">ssePath</span> <span class="ow">::</span> <span class="kt">SSE</span> <span class="kt">Path</span> <span class="ow">-></span> <span class="kt">Action</span>
<span class="nf">ssePath</span> <span class="p">(</span><span class="kt">SSEMessage</span> <span class="n">path</span><span class="p">)</span> <span class="ow">=</span> <span class="kt">RecvSse</span> <span class="p">(</span><span class="kt">Just</span> <span class="n">path</span><span class="p">)</span>
<span class="nf">ssePath</span> <span class="kr">_</span> <span class="ow">=</span> <span class="kt">RecvSse</span> <span class="kt">Nothing</span>
<span class="nf">drawPath</span> <span class="ow">::</span> <span class="kt">Context</span> <span class="ow">-></span> <span class="kt">Path</span> <span class="ow">-></span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">drawPath</span> <span class="n">ctx</span> <span class="n">points</span> <span class="ow">=</span>
<span class="n">when</span> <span class="p">(</span><span class="n">length</span> <span class="n">points</span> <span class="o">>=</span> <span class="mi">2</span><span class="p">)</span> <span class="o">$</span> <span class="kr">do</span>
<span class="kr">let</span> <span class="p">((</span><span class="n">x0</span><span class="p">,</span><span class="n">y0</span><span class="p">)</span><span class="kt">:</span><span class="n">ps</span><span class="p">)</span> <span class="ow">=</span> <span class="n">points</span>
<span class="n">lineWidth</span> <span class="mi">4</span> <span class="n">ctx</span>
<span class="n">beginPath</span> <span class="n">ctx</span>
<span class="n">moveTo</span> <span class="n">x0</span> <span class="n">y0</span> <span class="n">ctx</span>
<span class="n">mapM_</span> <span class="p">(</span><span class="nf">\</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">)</span> <span class="ow">-></span> <span class="n">lineTo</span> <span class="n">x</span> <span class="n">y</span> <span class="n">ctx</span><span class="p">)</span> <span class="n">ps</span>
<span class="n">stroke</span> <span class="n">ctx</span>
<span class="nf">foreign</span> <span class="kr">import</span> <span class="nn">javascript</span> <span class="n">unsafe</span> <span class="s">"$r = canvas_draw.getContext('2d');"</span>
<span class="n">jsCtx</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="kt">Context</span>
<span class="nf">foreign</span> <span class="kr">import</span> <span class="nn">javascript</span> <span class="n">unsafe</span> <span class="s">"$r = canvas_draw.clientWidth;"</span>
<span class="n">jsWidth</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="kt">Double</span>
<span class="nf">foreign</span> <span class="kr">import</span> <span class="nn">javascript</span> <span class="n">unsafe</span> <span class="s">"$r = canvas_draw.clientHeight;"</span>
<span class="n">jsHeight</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="kt">Double</span>
<span class="nf">foreign</span> <span class="kr">import</span> <span class="nn">javascript</span> <span class="n">unsafe</span> <span class="s">"$r = canvas_draw.getBoundingClientRect().left;"</span>
<span class="n">jsRectLeft</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="kt">Int</span>
<span class="nf">foreign</span> <span class="kr">import</span> <span class="nn">javascript</span> <span class="n">unsafe</span> <span class="s">"$r = canvas_draw.getBoundingClientRect().top;"</span>
<span class="n">jsRectTop</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="kt">Int</span></code></pre>
<p>Enfin, la fonction principale de l’application client regroupe ces éléments selon l’architecture demandée par Miso :</p>
<pre><code class="haskell"><span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="n">miso</span> <span class="o">$</span> <span class="n">const</span> <span class="kt">App</span>
<span class="p">{</span> <span class="n">initialAction</span> <span class="ow">=</span> <span class="kt">InitAllPaths</span>
<span class="p">,</span> <span class="n">model</span> <span class="ow">=</span> <span class="n">initialModel</span>
<span class="p">,</span> <span class="n">update</span> <span class="ow">=</span> <span class="n">updateModel</span>
<span class="p">,</span> <span class="n">view</span> <span class="ow">=</span> <span class="n">homeView</span>
<span class="p">,</span> <span class="n">events</span> <span class="ow">=</span> <span class="n">defaultEvents</span>
<span class="p">,</span> <span class="n">subs</span> <span class="ow">=</span> <span class="p">[</span>
<span class="n">sseSub</span> <span class="s">"/ssePath"</span> <span class="n">ssePath</span><span class="p">,</span> <span class="c1">-- register Server-Sent Events to the ssePath function</span>
<span class="n">mouseSub</span> <span class="kt">MouseMove</span> <span class="c1">-- register mouseSub events to the MouseMove action</span>
<span class="p">]</span>
<span class="p">,</span> <span class="n">mountPoint</span> <span class="ow">=</span> <span class="kt">Nothing</span>
<span class="p">}</span></code></pre>
<h3 id="toc-code-serveur-1">Code serveur</h3>
<p>Côté serveur (<a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint/isopaint_miso/src/server.hs"><code>src/server.hs</code></a>), on implémente un serveur Web classique. Il contient la liste des chemins dessinés par les clients et fournit une API Web ainsi qu’un système de notifications des clients (<em>Server‐Sent Events</em>), pour diffuser les nouveaux chemins dessinés.</p>
<pre><code class="haskell"><span class="nf">main</span> <span class="ow">::</span> <span class="kt">IO</span> <span class="nb">()</span>
<span class="nf">main</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">pathsRef</span> <span class="ow"><-</span> <span class="n">newIORef</span> <span class="kt">[]</span> <span class="c1">-- list of drawn paths</span>
<span class="n">chan</span> <span class="ow"><-</span> <span class="n">newChan</span> <span class="c1">-- Server-Sent Event handler</span>
<span class="n">run</span> <span class="mi">3000</span> <span class="o">$</span> <span class="n">logStdout</span> <span class="p">(</span><span class="n">serverApp</span> <span class="n">chan</span> <span class="n">pathsRef</span><span class="p">)</span> <span class="c1">-- run serverApp on port 3000</span>
<span class="c1">-- define the API type</span>
<span class="kr">type</span> <span class="kt">ServerApi</span>
<span class="ow">=</span> <span class="s">"static"</span> <span class="kt">:></span> <span class="kt">Raw</span> <span class="c1">-- "/static" endpoint, for static files</span>
<span class="kt">:<|></span> <span class="s">"ssePath"</span> <span class="kt">:></span> <span class="kt">Raw</span> <span class="c1">-- "/ssePath" endpoint, for registering SSE...</span>
<span class="kt">:<|></span> <span class="s">"xhrPath"</span> <span class="kt">:></span> <span class="kt">ReqBody</span> <span class="kt">'[JSON]</span> <span class="kt">Path</span> <span class="kt">:></span> <span class="kt">Post</span> <span class="kt">'[JSON]</span> <span class="kt">NoContent</span>
<span class="kt">:<|></span> <span class="s">"api"</span> <span class="kt">:></span> <span class="kt">Get</span> <span class="kt">'[JSON]</span> <span class="p">[</span><span class="kt">Path</span><span class="p">]</span>
<span class="kt">:<|></span> <span class="kt">ToServerRoutes</span> <span class="p">(</span><span class="kt">View</span> <span class="kt">Action</span><span class="p">)</span> <span class="kt">HtmlPage</span> <span class="kt">Action</span> <span class="c1">-- "/" endpoint</span>
<span class="c1">-- define a function for serving the API</span>
<span class="nf">serverApp</span> <span class="ow">::</span> <span class="kt">Chan</span> <span class="kt">ServerEvent</span> <span class="ow">-></span> <span class="kt">IORef</span> <span class="p">[</span><span class="kt">Path</span><span class="p">]</span> <span class="ow">-></span> <span class="kt">Application</span>
<span class="nf">serverApp</span> <span class="n">chan</span> <span class="n">pathsRef</span> <span class="ow">=</span> <span class="n">serve</span> <span class="p">(</span><span class="kt">Proxy</span> <span class="o">@</span><span class="kt">ServerApi</span><span class="p">)</span>
<span class="p">(</span> <span class="n">serveDirectoryFileServer</span> <span class="s">"static"</span> <span class="c1">-- serve the "/static" endpoint (using the "static" folder)</span>
<span class="kt">:<|></span> <span class="kt">Tagged</span> <span class="p">(</span><span class="n">eventSourceAppChan</span> <span class="n">chan</span><span class="p">)</span> <span class="c1">-- serve the "/ssePath" endpoint...</span>
<span class="kt">:<|></span> <span class="n">handleXhrPath</span> <span class="n">chan</span> <span class="n">pathsRef</span>
<span class="kt">:<|></span> <span class="n">handleApi</span> <span class="n">pathsRef</span>
<span class="kt">:<|></span> <span class="n">handleClientRoute</span>
<span class="p">)</span>
<span class="c1">-- when a path is sent to "/xhrPath", add the path in pathsRef and update clients using SSE</span>
<span class="nf">handleXhrPath</span> <span class="ow">::</span> <span class="kt">Chan</span> <span class="kt">ServerEvent</span> <span class="ow">-></span> <span class="kt">IORef</span> <span class="p">[</span><span class="kt">Path</span><span class="p">]</span> <span class="ow">-></span> <span class="kt">Path</span> <span class="ow">-></span> <span class="kt">Handler</span> <span class="kt">NoContent</span>
<span class="nf">handleXhrPath</span> <span class="n">chan</span> <span class="n">pathsRef</span> <span class="n">path</span> <span class="ow">=</span> <span class="kr">do</span>
<span class="n">liftIO</span> <span class="o">$</span> <span class="kr">do</span>
<span class="n">modifyIORef'</span> <span class="n">pathsRef</span> <span class="p">(</span><span class="nf">\</span> <span class="n">paths</span> <span class="ow">-></span> <span class="n">path</span><span class="kt">:</span><span class="n">paths</span><span class="p">)</span>
<span class="n">writeChan</span> <span class="n">chan</span> <span class="p">(</span><span class="kt">ServerEvent</span> <span class="kt">Nothing</span> <span class="kt">Nothing</span> <span class="p">[</span><span class="n">lazyByteString</span> <span class="o">$</span> <span class="n">encode</span> <span class="n">path</span><span class="p">])</span>
<span class="n">pure</span> <span class="kt">NoContent</span>
<span class="c1">-- when a client requests "/api", send all paths</span>
<span class="nf">handleApi</span> <span class="ow">::</span> <span class="kt">IORef</span> <span class="p">[</span><span class="kt">Path</span><span class="p">]</span> <span class="ow">-></span> <span class="kt">Handler</span> <span class="p">[</span><span class="kt">Path</span><span class="p">]</span>
<span class="nf">handleApi</span> <span class="n">pathsRef</span> <span class="ow">=</span> <span class="n">liftIO</span> <span class="p">(</span><span class="n">readIORef</span> <span class="n">pathsRef</span><span class="p">)</span>
<span class="c1">-- when a client requests "/", render and send the home view </span>
<span class="nf">handleClientRoute</span> <span class="ow">::</span> <span class="kt">Handler</span> <span class="p">(</span><span class="kt">HtmlPage</span> <span class="p">(</span><span class="kt">View</span> <span class="kt">Action</span><span class="p">))</span>
<span class="nf">handleClientRoute</span> <span class="ow">=</span> <span class="n">pure</span> <span class="o">$</span> <span class="kt">HtmlPage</span> <span class="o">$</span> <span class="n">homeView</span> <span class="n">initialModel</span></code></pre>
<p>On notera qu’on réutilise ici la fonction de rendu <code>homeView</code> pour générer la première vue de l’application. Cette vue est intégrée dans une page complète avec la fonction <code>toHtml</code> suivante :</p>
<pre><code class="haskell"><span class="kr">newtype</span> <span class="kt">HtmlPage</span> <span class="n">a</span> <span class="ow">=</span> <span class="kt">HtmlPage</span> <span class="n">a</span> <span class="kr">deriving</span> <span class="p">(</span><span class="kt">Show</span><span class="p">,</span> <span class="kt">Eq</span><span class="p">)</span>
<span class="kr">instance</span> <span class="kt">L</span><span class="o">.</span><span class="kt">ToHtml</span> <span class="n">a</span> <span class="ow">=></span> <span class="kt">L</span><span class="o">.</span><span class="kt">ToHtml</span> <span class="p">(</span><span class="kt">HtmlPage</span> <span class="n">a</span><span class="p">)</span> <span class="kr">where</span>
<span class="n">toHtmlRaw</span> <span class="ow">=</span> <span class="kt">L</span><span class="o">.</span><span class="n">toHtml</span>
<span class="c1">-- main function, for rendering a view to a HTML page</span>
<span class="n">toHtml</span> <span class="p">(</span><span class="kt">HtmlPage</span> <span class="n">x</span><span class="p">)</span> <span class="ow">=</span> <span class="kt">L</span><span class="o">.</span><span class="n">doctypehtml_</span> <span class="o">$</span> <span class="kr">do</span>
<span class="kt">L</span><span class="o">.</span><span class="n">head_</span> <span class="o">$</span> <span class="kr">do</span>
<span class="kt">L</span><span class="o">.</span><span class="n">meta_</span> <span class="p">[</span><span class="kt">L</span><span class="o">.</span><span class="n">charset_</span> <span class="s">"utf-8"</span><span class="p">]</span>
<span class="kt">L</span><span class="o">.</span><span class="n">with</span>
<span class="p">(</span><span class="kt">L</span><span class="o">.</span><span class="n">script_</span> <span class="n">mempty</span><span class="p">)</span>
<span class="p">[</span><span class="kt">L</span><span class="o">.</span><span class="n">src_</span> <span class="s">"static/all.js"</span><span class="p">,</span> <span class="kt">L</span><span class="o">.</span><span class="n">async_</span> <span class="n">mempty</span><span class="p">,</span> <span class="kt">L</span><span class="o">.</span><span class="n">defer_</span> <span class="n">mempty</span><span class="p">]</span>
<span class="kt">L</span><span class="o">.</span><span class="n">body_</span> <span class="p">(</span><span class="kt">L</span><span class="o">.</span><span class="n">toHtml</span> <span class="n">x</span><span class="p">)</span> <span class="c1">-- render the view and include it in the HTML page</span></code></pre>
<h2 id="toc-en-c-baséwidgets">En C++ « basé widgets »</h2>
<p>D’un point de vue utilisateur, une application Web et une application native sont assez similaires. Il s’agit essentiellement d’une interface utilisateur graphique (dans un navigateur ou dans une fenêtre) qui interagit avec un programme principal (un programme serveur ou une autre partie du même programme). Ainsi, les cadriciels Web basés <em>widgets</em>, comme <a href="https://www.webtoolkit.eu/wt">Wt</a>, reprennent logiquement la même architecture que les cadriciels natifs, comme Qt ou GTK. Le développeur écrit un programme classique où l’interface graphique est définie via des <em>widgets</em> ; le cadriciels se charge de construire l’interface dans le navigateur client et de gérer les connexions réseau. En pratique, cette architecture n’est pas complètement transparente et le développeur doit tout de même tenir compte de l’aspect réseau de l’application.</p>
<h3 id="toc-application-principale">Application principale</h3>
<p>Pour implémenter l’application de dessin collaboratif avec Wt, on peut définir le programme principal suivant (<a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint/isopaint_wt/src/isopaint.cpp"><code>src/isopaint.cpp</code></a>). Ici, on a choisi d’organiser le code selon une architecture de type <a href="https://fr.wikipedia.org/wiki/Mod%C3%A8le-vue-contr%C3%B4leur" title="modèle‐vue‐contrôleur">MVC</a>. Le contrôleur fait le lien entre le modèle (les chemins dessinés par les clients) et les vues. Les vues correspondent aux clients qui se connectent, c’est pourquoi on les construit à la demande, via la fonction lambda <code>mkApp</code>.</p>
<pre><code class="c++"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// controller: handle client connections and data (drawn paths)</span>
<span class="n">Controller</span> <span class="n">controller</span><span class="p">;</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">WServer</span> <span class="n">server</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="n">WTHTTP_CONFIGURATION</span><span class="p">);</span>
<span class="c1">// endpoint "/": create a client app and register connection in the controller</span>
<span class="k">auto</span> <span class="n">mkApp</span> <span class="o">=</span> <span class="p">[</span><span class="o">&</span><span class="n">controller</span><span class="p">]</span> <span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WEnvironment</span> <span class="o">&</span> <span class="n">env</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o"><</span><span class="n">AppDrawing</span><span class="o">></span><span class="p">(</span><span class="n">env</span><span class="p">,</span> <span class="n">controller</span><span class="p">);</span>
<span class="p">};</span>
<span class="n">server</span><span class="p">.</span><span class="n">addEntryPoint</span><span class="p">(</span><span class="n">Wt</span><span class="o">::</span><span class="n">EntryPointType</span><span class="o">::</span><span class="n">Application</span><span class="p">,</span> <span class="n">mkApp</span><span class="p">,</span> <span class="s">"/"</span><span class="p">);</span>
<span class="n">server</span><span class="p">.</span><span class="n">run</span><span class="p">();</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre>
<h3 id="toc-contrôleur">Contrôleur</h3>
<p>Le contrôleur gère le modèle et les connexions client (<a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint/isopaint_wt/src/Controller.hpp"><code>src/Controller.hpp</code></a>). Comme ici le modèle est très simple (un tableau de chemins), on l’implémente par un attribut du contrôleur. Quelques méthodes permettent d’implémenter le protocole de communication avec les clients : connexion, déconnexion, accès au tableau de chemins, ajout d’un nouveau chemin. Enfin, on utilise un <a href="https://fr.wikipedia.org/wiki/Exclusion_mutuelle"><em>mutex</em></a> pour que l’application puisse s’exécuter en multiple fils d’exécution (<em>multi‐thread</em>). </p>
<pre><code class="c++"><span class="k">class</span> <span class="nc">Controller</span> <span class="p">{</span>
<span class="k">private</span><span class="o">:</span>
<span class="k">mutable</span> <span class="n">std</span><span class="o">::</span><span class="n">mutex</span> <span class="n">_mutex</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span><span class="o">></span> <span class="n">_paths</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">map</span><span class="o"><</span><span class="n">AppDrawing</span><span class="o">*</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">></span> <span class="n">_connections</span><span class="p">;</span>
<span class="k">public</span><span class="o">:</span>
<span class="c1">// register client app</span>
<span class="kt">void</span> <span class="n">addClient</span><span class="p">(</span><span class="n">AppDrawing</span> <span class="o">*</span> <span class="n">app</span><span class="p">)</span> <span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_lock</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">></span> <span class="n">lock</span><span class="p">(</span><span class="n">_mutex</span><span class="p">);</span>
<span class="n">_connections</span><span class="p">[</span><span class="n">app</span><span class="p">]</span> <span class="o">=</span> <span class="n">app</span><span class="o">-></span><span class="n">instance</span><span class="p">()</span><span class="o">-></span><span class="n">sessionId</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">// unregister client app</span>
<span class="kt">void</span> <span class="n">removeClient</span><span class="p">(</span><span class="n">AppDrawing</span> <span class="o">*</span> <span class="n">app</span><span class="p">)</span> <span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_lock</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">></span> <span class="n">lock</span><span class="p">(</span><span class="n">_mutex</span><span class="p">);</span>
<span class="n">_connections</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">app</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// get all paths</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span><span class="o">></span> <span class="n">getPaths</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_lock</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">></span> <span class="n">lock</span><span class="p">(</span><span class="n">_mutex</span><span class="p">);</span>
<span class="k">return</span> <span class="n">_paths</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// add a new path and update all client apps</span>
<span class="kt">void</span> <span class="n">addPath</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span> <span class="o">&</span> <span class="n">path</span><span class="p">)</span> <span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_lock</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">></span> <span class="n">lock</span><span class="p">(</span><span class="n">_mutex</span><span class="p">);</span>
<span class="n">_paths</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">path</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&</span> <span class="nl">conn</span> <span class="p">:</span> <span class="n">_connections</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">updateFunc</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&</span><span class="n">AppDrawing</span><span class="o">::</span><span class="n">addPath</span><span class="p">,</span> <span class="n">conn</span><span class="p">.</span><span class="n">first</span><span class="p">,</span> <span class="n">path</span><span class="p">);</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">WServer</span><span class="o">::</span><span class="n">instance</span><span class="p">()</span><span class="o">-></span><span class="n">post</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">second</span><span class="p">,</span> <span class="n">updateFunc</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">};</span></code></pre>
<h3 id="toc-application-de-dessin">Application de dessin</h3>
<p>Pour implémenter l’application de dessin proprement dite (<a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint/isopaint_wt/src/AppDrawing.hpp"><code>src/AppDrawing.hpp</code></a> et <a href="https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_33/isopaint/isopaint_wt/src/AppDrawing.cpp"><code>src/AppDrawing.cpp</code></a>), on utilise les <em>widgets</em> fournis par Wt. Cette application va donner lieu à une interface graphique côté client, avec des communications réseau, mais ceci reste transparent pour le développeur, qui manipule du code orienté objet classique. Par exemple, l’application client expose une fonction <code>addPath</code> qui permet au serveur d’envoyer un nouveau chemin au client, via un appel de méthode classique.</p>
<pre><code class="c++"><span class="c1">// headers</span>
<span class="k">class</span> <span class="nc">AppDrawing</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WApplication</span> <span class="p">{</span>
<span class="k">private</span><span class="o">:</span>
<span class="n">Controller</span> <span class="o">&</span> <span class="n">_controller</span><span class="p">;</span>
<span class="n">Painter</span> <span class="o">*</span> <span class="n">_painter</span><span class="p">;</span>
<span class="k">public</span><span class="o">:</span>
<span class="n">AppDrawing</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WEnvironment</span> <span class="o">&</span> <span class="n">env</span><span class="p">,</span> <span class="n">Controller</span> <span class="o">&</span> <span class="n">controller</span><span class="p">);</span>
<span class="o">~</span><span class="n">AppDrawing</span><span class="p">();</span>
<span class="c1">// add a path (sent by the server)</span>
<span class="kt">void</span> <span class="nf">addPath</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span> <span class="o">&</span> <span class="n">path</span><span class="p">);</span>
<span class="p">};</span>
<span class="c1">// implementation</span>
<span class="n">AppDrawing</span><span class="o">::</span><span class="n">AppDrawing</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WEnvironment</span> <span class="o">&</span> <span class="n">env</span><span class="p">,</span> <span class="n">Controller</span> <span class="o">&</span> <span class="n">controller</span><span class="p">)</span> <span class="o">:</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">WApplication</span><span class="p">(</span><span class="n">env</span><span class="p">),</span> <span class="n">_controller</span><span class="p">(</span><span class="n">controller</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// build the interface</span>
<span class="n">root</span><span class="p">()</span><span class="o">-></span><span class="n">addWidget</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o"><</span><span class="n">Wt</span><span class="o">::</span><span class="n">WText</span><span class="o">></span><span class="p">(</span><span class="s">"isopaint_wt "</span><span class="p">));</span>
<span class="n">root</span><span class="p">()</span><span class="o">-></span><span class="n">addWidget</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o"><</span><span class="n">Wt</span><span class="o">::</span><span class="n">WBreak</span><span class="o">></span><span class="p">());</span>
<span class="n">_painter</span> <span class="o">=</span> <span class="n">root</span><span class="p">()</span><span class="o">-></span><span class="n">addWidget</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o"><</span><span class="n">Painter</span><span class="o">></span><span class="p">(</span><span class="n">controller</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="mi">300</span><span class="p">));</span>
<span class="c1">// register the client in the controller</span>
<span class="n">_controller</span><span class="p">.</span><span class="n">addClient</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="c1">// enable updates from the server</span>
<span class="n">enableUpdates</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">AppDrawing</span><span class="o">::~</span><span class="n">AppDrawing</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// unregister the client</span>
<span class="n">_controller</span><span class="p">.</span><span class="n">removeClient</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">AppDrawing</span><span class="o">::</span><span class="n">addPath</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span> <span class="o">&</span> <span class="n">path</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// add a path sent by the server</span>
<span class="n">_painter</span><span class="o">-></span><span class="n">addPath</span><span class="p">(</span><span class="n">path</span><span class="p">);</span>
<span class="c1">// request updating the interface</span>
<span class="n">triggerUpdate</span><span class="p">();</span>
<span class="p">}</span></code></pre>
<p>Enfin, pour implémenter la zone de dessin, on dérive notre propre <em>widget</em>, et on redéfinit son affichage, sa gestion d’événements…</p>
<pre><code class="c++"><span class="c1">// headers</span>
<span class="k">class</span> <span class="nc">Painter</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WPaintedWidget</span> <span class="p">{</span>
<span class="k">protected</span><span class="o">:</span>
<span class="n">Controller</span> <span class="o">&</span> <span class="n">_controller</span><span class="p">;</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">WPen</span> <span class="n">_pen</span><span class="p">;</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span> <span class="n">_currentPath</span><span class="p">;</span> <span class="c1">// local path (being drawn by the user)</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span><span class="o">></span> <span class="n">_paths</span><span class="p">;</span> <span class="c1">// common paths (sent by the server)</span>
<span class="k">public</span><span class="o">:</span>
<span class="n">Painter</span><span class="p">(</span><span class="n">Controller</span> <span class="o">&</span> <span class="n">controller</span><span class="p">,</span> <span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">height</span><span class="p">);</span>
<span class="c1">// add a path (sent by the server)</span>
<span class="kt">void</span> <span class="nf">addPath</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span> <span class="o">&</span> <span class="n">path</span><span class="p">);</span>
<span class="k">private</span><span class="o">:</span>
<span class="c1">// main display function</span>
<span class="kt">void</span> <span class="n">paintEvent</span><span class="p">(</span><span class="n">Wt</span><span class="o">::</span><span class="n">WPaintDevice</span> <span class="o">*</span> <span class="n">paintDevice</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
<span class="c1">// callback functions for handling mouse events</span>
<span class="kt">void</span> <span class="nf">mouseDown</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WMouseEvent</span> <span class="o">&</span> <span class="n">e</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">mouseUp</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WMouseEvent</span> <span class="o">&</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">mouseDrag</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WMouseEvent</span> <span class="o">&</span> <span class="n">e</span><span class="p">);</span>
<span class="p">};</span>
<span class="c1">// implementation</span>
<span class="n">Painter</span><span class="o">::</span><span class="n">Painter</span><span class="p">(</span><span class="n">Controller</span> <span class="o">&</span> <span class="n">controller</span><span class="p">,</span> <span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">height</span><span class="p">)</span> <span class="o">:</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">WPaintedWidget</span><span class="p">(),</span>
<span class="n">_controller</span><span class="p">(</span><span class="n">controller</span><span class="p">),</span>
<span class="n">_paths</span><span class="p">(</span><span class="n">controller</span><span class="p">.</span><span class="n">getPaths</span><span class="p">())</span>
<span class="p">{</span>
<span class="c1">// initialize the widget</span>
<span class="n">resize</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">);</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">WCssDecorationStyle</span> <span class="n">deco</span><span class="p">;</span>
<span class="n">deco</span><span class="p">.</span><span class="n">setBorder</span><span class="p">(</span><span class="n">Wt</span><span class="o">::</span><span class="n">WBorder</span><span class="p">(</span><span class="n">Wt</span><span class="o">::</span><span class="n">BorderStyle</span><span class="o">::</span><span class="n">Solid</span><span class="p">));</span>
<span class="n">setDecorationStyle</span><span class="p">(</span><span class="n">deco</span><span class="p">);</span>
<span class="c1">// initialize the pen</span>
<span class="n">_pen</span><span class="p">.</span><span class="n">setCapStyle</span><span class="p">(</span><span class="n">Wt</span><span class="o">::</span><span class="n">PenCapStyle</span><span class="o">::</span><span class="n">Round</span><span class="p">);</span>
<span class="n">_pen</span><span class="p">.</span><span class="n">setJoinStyle</span><span class="p">(</span><span class="n">Wt</span><span class="o">::</span><span class="n">PenJoinStyle</span><span class="o">::</span><span class="n">Round</span><span class="p">);</span>
<span class="n">_pen</span><span class="p">.</span><span class="n">setWidth</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
<span class="c1">// connect callback functions (for handling mouse events)</span>
<span class="n">mouseDragged</span><span class="p">().</span><span class="n">connect</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="o">&</span><span class="n">Painter</span><span class="o">::</span><span class="n">mouseDrag</span><span class="p">);</span>
<span class="n">mouseWentDown</span><span class="p">().</span><span class="n">connect</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="o">&</span><span class="n">Painter</span><span class="o">::</span><span class="n">mouseDown</span><span class="p">);</span>
<span class="n">mouseWentUp</span><span class="p">().</span><span class="n">connect</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="o">&</span><span class="n">Painter</span><span class="o">::</span><span class="n">mouseUp</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">Painter</span><span class="o">::</span><span class="n">addPath</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span> <span class="o">&</span> <span class="n">path</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// add a path (sent by the server)</span>
<span class="n">_paths</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">path</span><span class="p">);</span>
<span class="c1">// update widget</span>
<span class="n">update</span><span class="p">();</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">Painter</span><span class="o">::</span><span class="n">paintEvent</span><span class="p">(</span><span class="n">Wt</span><span class="o">::</span><span class="n">WPaintDevice</span> <span class="o">*</span> <span class="n">paintDevice</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">WPainter</span> <span class="n">painter</span><span class="p">(</span><span class="n">paintDevice</span><span class="p">);</span>
<span class="n">painter</span><span class="p">.</span><span class="n">setPen</span><span class="p">(</span><span class="n">_pen</span><span class="p">);</span>
<span class="c1">// draw common paths (sent by the server)</span>
<span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="k">auto</span> <span class="o">&</span> <span class="nl">p</span> <span class="p">:</span> <span class="n">_paths</span><span class="p">)</span>
<span class="n">painter</span><span class="p">.</span><span class="n">drawPath</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="c1">// draw the local path (being drawn by the user)</span>
<span class="n">painter</span><span class="p">.</span><span class="n">drawPath</span><span class="p">(</span><span class="n">_currentPath</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">Painter</span><span class="o">::</span><span class="n">mouseDown</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WMouseEvent</span> <span class="o">&</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// begin drawing a path: remember the initial position</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">Coordinates</span> <span class="n">c</span> <span class="o">=</span> <span class="n">e</span><span class="p">.</span><span class="n">widget</span><span class="p">();</span>
<span class="n">_currentPath</span> <span class="o">=</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span><span class="p">(</span><span class="n">Wt</span><span class="o">::</span><span class="n">WPointF</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">c</span><span class="p">.</span><span class="n">y</span><span class="p">));</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">Painter</span><span class="o">::</span><span class="n">mouseDrag</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WMouseEvent</span> <span class="o">&</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// mouse move: add the new position into the current path</span>
<span class="n">Wt</span><span class="o">::</span><span class="n">Coordinates</span> <span class="n">c</span> <span class="o">=</span> <span class="n">e</span><span class="p">.</span><span class="n">widget</span><span class="p">();</span>
<span class="n">_currentPath</span><span class="p">.</span><span class="n">lineTo</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">c</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
<span class="c1">// update widget</span>
<span class="n">update</span><span class="p">();</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">Painter</span><span class="o">::</span><span class="n">mouseUp</span><span class="p">(</span><span class="k">const</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WMouseEvent</span> <span class="o">&</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// send the path to the server, which will send it to the clients</span>
<span class="n">_controller</span><span class="p">.</span><span class="n">addPath</span><span class="p">(</span><span class="n">_currentPath</span><span class="p">);</span>
<span class="c1">// reset current path</span>
<span class="n">_currentPath</span> <span class="o">=</span> <span class="n">Wt</span><span class="o">::</span><span class="n">WPainterPath</span><span class="p">();</span>
<span class="p">}</span></code></pre>
<h2 id="toc-conclusion">Conclusion</h2>
<p>À travers cette petite application de dessin collaboratif, nous avons vu qu’il est possible de développer des applications Web « <em>fullstack</em> », où un même code peut s’exécuter à la fois côté client et côté serveur. Pour cela, il existe différents types de cadriciels, notamment « isomorphiques » et « basés <em>widgets</em> ».</p>
<p>Les applications isomorphiques sont une évolution assez naturelle des applications clientes mono‐pages couplées à des serveurs d’API Web. Il s’agit principalement de réutiliser le code client côté serveur, pour générer la première vue de l’application avant le téléchargement complet de l’application client. Tout ceci repose sur des technologies Web classiques et est assez simple à mettre en place. De plus, les cadriciels proposent généralement une architecture de code classique (MVC, Flux…) qui permet de développer rapidement des applications.</p>
<p>Les cadriciels basés <em>widgets</em> suivent une approche différente mais également intéressante : porter l’architecture des interfaces graphiques utilisateur au monde du Web. Ces architectures sont familières aux développeurs d’applications natives et sont très adaptées aux langages orientés objets. Sans être complètement masqué, l’aspect réseau est en grande partie géré par le cadriciel, ce qui facilite le développement.</p>
<p>Enfin, concernant les langages, si JavaScript a l’avantage d’être compréhensible directement par les navigateurs, d’autres langages sont également utilisables, via une étape de transpilation vers JavaScript. On notera que les langages compilés permettent de détecter certaines erreurs plus rapidement et que les langages fonctionnels (avec fonctions pures, données immutables…) réduisent les erreurs potentielles.</p>
</div><div><a href="https://linuxfr.org/news/developpement-web-fullstack-application-de-dessin-collaboratif.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/116595/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/developpement-web-fullstack-application-de-dessin-collaboratif#comments">ouvrir dans le navigateur</a>
</p>
nokomprendobubar🦥Davy Defaudpalm123ZeroHeureBruno Michelhttps://linuxfr.org/nodes/116595/comments.atom