tag:linuxfr.org,2005:/users/mgautierLinuxFr.org : les contenus de GaMa2024-03-27T20:24:06+01:00/favicon.pngtag:linuxfr.org,2005:Diary/411092024-03-22T12:44:38+01:002024-03-22T12:44:38+01:00PySimpleGUI ferme (les sources)Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>La librairie python <code>PySimpleGui</code> qui est une surcouche a TkInter proposait (et propose toujours) une approche plus simple pour la création d'interface graphique en Python.</p>
<p>Elle était sous license LGPL et bénéficiait d'un certain succès. On en a même parlé ici (<a href="//linuxfr.org/news/pysimplegui-prenez-plaisir-a-faire-des-interfaces-graphiques-en-python">https://linuxfr.org/news/pysimplegui-prenez-plaisir-a-faire-des-interfaces-graphiques-en-python</a>).</p>
<p>Le mois dernier, son auteur a décidé de passer PySimpleGUI sous une licence propriétaire. Encore mieux, il a supprimer tout l'historique et le dépot github contient maintenant que des commits qui datent d'il y a un mois.</p>
<p>Heureusement un fork existe: <a href="https://github.com/andor-pierdelacabeza/PySimpleGUI-4-foss">https://github.com/andor-pierdelacabeza/PySimpleGUI-4-foss</a></p>
<p>En vrai, on aurait du s'en douter en voyant que <a href="https://github.com/andor-pierdelacabeza/PySimpleGUI-4-foss/blob/acde437de74cff41c817b8e657a4b441d01564d8/CONTRIBUTING.md">le fichier <code>CONTRIBUTING.md</code></a> du le projet explicitait qu'il refusait les contributions externes et que <code>PySimpleGUI is different than most projects on GitHub. It is licensed using the "Open Source License" LGPL3. However, the coding and development of the project is not "open source".</code></p>
<p>Pour ma part, j'ai utilisé PySimpleGui sur un projet pour un de mes clients. J'ai pas mal aimé au début mais j'ai trouvé que la définition d'interfaces à base de listes imbriquées ne passaient pas trop à l'échelle. J'avais rapidement l'impression de faire du lisp.</p>
<p>Et le style "direct" (en opposition à la programmation événementielle) était là aussi plus simple au début mais devenait un vrai problème avec une interface complexe. Du coup je pensais pas y revenir, avec le changement de licence, c'est confirmé.</p>
<p>Discussion sur hacker news: <a href="https://news.ycombinator.com/item?id=39369353">https://news.ycombinator.com/item?id=39369353</a><br>
(Qui semble être l'annonce la plus officielle trouvable)</p>
<div><a href="https://linuxfr.org/users/mgautier/journaux/pysimplegui-ferme-les-sources.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/135188/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/mgautier/journaux/pysimplegui-ferme-les-sources#comments">ouvrir dans le navigateur</a>
</p>
GaMahttps://linuxfr.org/nodes/135188/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:Diary/358722015-05-21T15:15:26+02:002015-05-21T15:15:26+02:00Présentation de Devparrot à l'afpyro Lyon le mercredi 27 mai.Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>Bonjour à tous,</p>
<p>Il y a déjà un lien glissé dans les commentaire de ce <a href="//linuxfr.org/users/lebouquetin/journaux/utilisateurs-python-sur-grenoble-rendez-vous-jeudi-28-mai-pour-la-presentation-de-tortilla">journal sur la présentation de tortilla</a> qui en parle, mais je le remet un peu en avant.</p>
<p>Ce mercredi 27 mai, à lyon (<a href="http://www.legitenumerique.com/fr/">au gîte numérique</a>), je présenterai pour la première fois (pression, pression) mon éditeur de texte <a href="http://www.devparrot.org/">Devparrot</a>. Il y a un <a href="http://www.meetup.com/legitenumerique/events/222015503/">meetup</a> créé si vous voulez vous inscrire.</p>
<p>Devparrot est un éditeur de texte écrit en python. Il se veut grandement personnalisable et extensible.<br>
Ça fait un bout de temps que je travail dessus (4 ans déjà…) mais, à ma connaissance, je suis le seul à l'utiliser. La faute au manque de communication évident.</p>
<p>Le peu d'utilisateurs fait que le nombre de modules est grandement réduit (comment ça à son strict minimum ?) et que les retours de bug sont limités (mais très rapidement corrigé).</p>
<p>Si vous pouvez venir mercredi prochain, on en discutera autour d'une bière.<br>
Sinon, et si vous le testez quand même, n'hésitez pas à faire des retours et soyez indulgent, vous êtes peut-être le deuxième utilisateur (après moi) de Devparrot :)</p><div><a href="https://linuxfr.org/users/mgautier/journaux/presentation-de-devparrot-a-l-afpyro-lyon-le-mercredi-27-mai.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/105812/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/mgautier/journaux/presentation-de-devparrot-a-l-afpyro-lyon-le-mercredi-27-mai#comments">ouvrir dans le navigateur</a>
</p>
GaMahttps://linuxfr.org/nodes/105812/comments.atomtag:linuxfr.org,2005:Diary/356182015-02-16T17:27:24+01:002015-02-16T17:27:24+01:00Ebooks technique gratuitsLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<p>La maison d'édition packt re-propose leur offre commercial de plusieurs livres gratuits pendant un temps limité.</p>
<p>On en avait déjà parlé <a href="//linuxfr.org/users/lebouquetin/journaux/calendrier-de-l-avent-vite-des-ebooks-techniques-gratuits">ici</a> à l'occasion de leur "calendrier de l'avent".</p>
<p>Chaque livre est disponible gratuitement (sans drm) pendant 24h.<br>
Ça commence aujourd'hui et le premier livre est "Drupal 7 Module Development".</p>
<p>Notez bien que c'est l'offre qui est limité dans le temps. Vous pourrez lire le livre quand vous voudrez.</p>
<p>Avis aux intéressés…</p><div><a href="https://linuxfr.org/users/mgautier/journaux/ebooks-technique-gratuits.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/104819/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/mgautier/journaux/ebooks-technique-gratuits#comments">ouvrir dans le navigateur</a>
</p>
GaMahttps://linuxfr.org/nodes/104819/comments.atomtag:linuxfr.org,2005:News/356262014-08-29T23:29:17+02:002014-08-30T20:05:57+02:00Museomix cherche des développeursLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Museomix est un évènement qui rassemble, le temps d’un week‐end (7, 8, 9 novembre) dans un musée, des amateurs, curieux, passionnés de différents profils, pour remixer un musée.</p>
<p>Sur le modèle des <a href="http://fr.wikipedia.org/wiki/Hackathon"><em>hackathons</em></a>, des équipes sont constituées pour travailler pendant trois jours sur un prototype de support de médiation dans le musée. Un des exemples de réalisation est le prototype développé l’an dernier au <a href="http://fr.wikipedia.org/wiki/Mus%C3%A9e_dauphinois">Musée dauphinois</a> à Grenoble : <a href="http://www.museomix.org/prototypes/st-francois-de-sales-guide-de-la-chapelle/"><em>Levez les yeux !</em></a> (<a href="https://www.youtube.com/watch?v=gcRWYlRiQ28">Vidéo Youtube</a>).</p>
<p>Il manque des développeurs dans quasiment tous les muséomix : Saint‐Étienne, Paris, Arles, Nantes, Lille, Genève, Montréal et Derby (Angleterre).</p></div><ul><li>lien nᵒ 1 : <a title="http://www.museomix.org/" hreflang="fr" href="https://linuxfr.org/redirect/91345">MuséoMix</a></li><li>lien nᵒ 2 : <a title="http://www.musee-art-industrie.saint-etienne.fr" hreflang="fr" href="https://linuxfr.org/redirect/91346">Musée d’art et d’industrie de Saint‐Étienne</a></li></ul><div><p>Chaque équipe est constituée des profils suivants :</p>
<ul>
<li>Fabrication — Fabriquez‐le !</li>
<li>Médiation, interaction et usage – Faites une expérience réussie !</li>
<li>Expertise des contenus – Rendez‐le scientifiquement juste !</li>
<li>Communication et diffusion – Faites‐le connaître !</li>
<li>Programmation et développement – Faites‐le marcher !</li>
<li>Graphisme et mise en forme – Rendez‐le beau !</li>
<li>+ un profil polyvalent.</li>
</ul><p>Les équipes seront libres de choisir l’œuvre à « museomixer ».</p>
<p>En région Rhône‐Alpes, l’édition 2014 aura lieu au <a href="http://www.musee-art-industrie.saint-etienne.fr/">Musée d’art et d’industrie de Saint‐Étienne</a>.</p>
<p>Il contient trois collections :</p>
<ul>
<li>une sur les armes (du moyen‐âge au contemporain) ;</li>
<li>une sur les cycles (vélo, grand‐bi…) ;</li>
<li>et une sur les rubans (avec notamment de nombreux métiers à tisser).</li>
</ul><p>Nous avons reçu de nombreuses candidatures dans tous les profils, mais nous manquons de développeurs ! Si le projet vous intéresse, n’hésitez pas à nous envoyer un courriel pour avoir plus d’informations à <em>clemence<at>cybele-arts<dot>fr</em>. Vous n’avez pas besoin d’avoir des connaissances particulières en art, ou sur le domaine des musées en général. Un peu de curiosité et d’envie suffiront !</p>
<p>Détails pratiques :</p>
<ul>
<li>présence obligatoire les trois jours : 7, 8 et 9 novembre 2014 ;</li>
<li>50 € (ou équivalent selon les pays) de participation sont demandés pour les repas des trois jours qui seront servis ;</li>
<li>chaque participant doit se loger par ses propres moyens (en région Rhône‐Alpes, des propositions de <a href="http://fr.wikipedia.org/wiki/CouchSurfing#Signification_du_terme" title="service d’hébergement"><em>CouchSurfing</em></a> seront arrangées entre les muséomixeurs locaux et les autres) ;</li>
<li>des <a href="http://fr.wikipedia.org/wiki/Fab_lab" title="fabrication laboratory — laboratoire de fabrication"><em>fab labs</em></a> locaux seront présents pour mettre tout leur matériel à disposition.</li>
</ul><p>N. D. M. : Voir la partie « <a href="http://www.museomix.org/comment-fonctionne-museomix/"><em>Comment fonctionne museomix ?</em></a> sur la question des licences (« <em>Nous encourageons les participants à adopter des licences libres (ou ouvertes à défaut)</em> », pas de brevets, les licences CC-0, CC-By, CC-by-sa, LGPL, CeCILL, Peer Production sont citées, et les briques propriétaires devraient pouvoir être remplacées par une alternative libre). Et dans la partie « <a href="http://www.museomix.org/a-propos/"><em>Inspiration</em></a> » sont mentionnés les <a href="http://drupal.org/principles">principes Drupal</a>, le <a href="http://www.mozilla.org/about/manifesto.fr.html">Manifeste Mozilla</a>, les <a href="http://en.wikipedia.org/wiki/Template:Wikipedia_principles">Principes de Wikipédia</a>, le <a href="http://www.debian.org/social_contract">Contrat social Debian</a> et les <a href="http://www.burningman.com/whatisburningman/about_burningman/principles.html">Principes de l’événement <em>Burning Man</em></a>.</p></div><div><a href="https://linuxfr.org/news/museomix-cherche-des-developpeurs.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/103130/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/museomix-cherche-des-developpeurs#comments">ouvrir dans le navigateur</a>
</p>
GaMaDavy DefaudNils RatusznikBenoît Sibaudpalm123https://linuxfr.org/nodes/103130/comments.atomtag:linuxfr.org,2005:Diary/341492013-07-23T00:59:56+02:002013-07-23T00:59:56+02:00Présentation d'idée : PGPIDLicence CC By‑SA http://creativecommons.org/licenses/by-sa/3.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#pourquoi-pgpid">Pourquoi PGPID</a></li>
<li>
<a href="#pr%C3%A9sentation">Présentation</a><ul>
<li>
<a href="#d%C3%A9finitions">Définitions</a><ul>
<li><a href="#service">Service</a></li>
<li><a href="#sn-service-name">SN (Service Name)</a></li>
<li><a href="#pgpid">PGPID</a></li>
<li><a href="#pgpidc">PGPIDC</a></li>
<li><a href="#spn-ou-node">SPN (ou Node)</a></li>
<li><a href="#nds">NDS</a></li>
<li><a href="#ndn">NDN</a></li>
<li><a href="#sds">SDS</a></li>
<li><a href="#ts">TS</a></li>
<li><a href="#tn">TN</a></li>
<li><a href="#fpgpid">FPGPID</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#identification">Identification</a></li>
<li>
<a href="#use-case">Use Case</a><ul>
<li><a href="#inscription">Inscription</a></li>
<li><a href="#identification-sur-les-sites-web"> Identification sur les sites web</a></li>
<li><a href="#participation-au-r%C3%A9seau-pgpid">Participation au réseau PGPID</a></li>
<li><a href="#installer-et-fournir-de-nouveaux-services">Installer et fournir de nouveaux services</a></li>
<li><a href="#installer-un-service-de-messagerie-instantan%C3%A9-im">Installer un service de messagerie instantané (IM)</a></li>
<li><a href="#communiquer-avec-bob">Communiquer avec Bob</a></li>
<li><a href="#avoir-un-deuxi%C3%A8me-spn">Avoir un deuxième SPN</a></li>
<li><a href="#id%C3%A9es-de-service-possible">Idées de service possible</a></li>
</ul>
</li>
<li>
<a href="#utilisation-avanc%C3%A9e">Utilisation avancée</a><ul>
<li><a href="#avoir-plusieurs-ndn">Avoir plusieurs NDN</a></li>
<li><a href="#itin%C3%A9rance">Itinérance</a></li>
<li><a href="#r%C3%A9silience-des-donn%C3%A9es">Résilience des données</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
<li><a href="#conclusion-bis-aka-vaporware">Conclusion bis (aka vaporware)</a></li>
</ul><p>Bien le bonjour à toi journal (ainsi qu'à tes lecteurs),</p>
<p>Aujourd'hui, pour mon premier journal, je m'en vais vous présenter une idée qui me taraude depuis bien longtemps.<br>
Vous vous en doutez, vous avez lu le titre, c'est de PGPID dont je vais vous parler.<br>
Avant d'aller plus loin, je ne vais pas vous tromper sur la marchandise : Il y a pas de code, pas de soft à tester. C'est juste une idée jetée sur un clavier.</p>
<h2 id="pourquoi-pgpid">Pourquoi PGPID</h2>
<p>On parle de plus en plus (ici en tout cas) de réseaux sociaux décentralisés.<br>
Certains ne jurent que par eux (quid de la maitrise de ses données ?), d'autres sont plus circonspects (quid de la disponibilité ?).</p>
<p>Il existe de nombreuses solutions pour faire du réseau social plus ou moins décentralisé/a-centralisé/fédéré.<br>
Les citer serait trop long, sans intérêt et ceux que j'oublierai se sentiraient exclus et se vexeraient. Alors, je ne le ferais pas :)</p>
<p>Ces solutions marchent bien mais, de mon point de vue, elles souffrent de deux problèmes communs :</p>
<ul>
<li>l'identification des utilisateurs.</li>
<li>la localisation des machines fournissant un/des service(s) auprès des utilisateurs.</li>
</ul><p>La plupart utilisent un identifiant d'utilisateur de la forme "<a href="mailto:utilisateur@fournisseur.tld">utilisateur@fournisseur.tld</a>" ou "fournisseur.tld/utilisateur"<br>
Le problème de ces identifiants est qu'ils sont dépendant du fournisseur.<br>
Ainsi :</p>
<ul>
<li>C'est le DNS du fournisseur qui permet de localiser la machine qui va bien.</li>
<li>Pour deux services différents chez deux fournisseurs différents il y aura deux identifiants.</li>
<li>Le jour où mon fournisseur ferme ou que je veux en changer, il me faut changer de fournisseur.</li>
</ul><p>La solution couramment évoquée est d'avoir son propre nom de domaine. Honnêtement, j'y crois pas beaucoup. Malgré l'infinité de possibilité de nom de domaine, les domaines intéressants sont eux limités.<br>
(Mon nom de famille est déjà pris par une société. Et même si j'aurai pu l'avoir il y a pas mal de monde qui a le même nom que moi).<br>
Quant bien même, un tld intéressant serait disponible, il n'est pas donné d'en acheter un (vous voyez votre grand-mère aller sur un registar acheter "tante-Huguette.fr" ?).</p>
<p>Et les noms de domaine ont un sens. Les goûts et les couleurs changeant avec le temps, que faire de ce sens ? <br>
Tous ces problèmes m'amène à penser que les noms de domaine ne sont pas une solution à long terme<br>
(ça marche bien pour un nombre limité d'utilisateurs, pour 6 milliards de personnes c'est plus problématique)</p>
<p>On pourra cependant noter <a href="http://retroshare.sourceforge.net/">RetroShare</a> (le seul que je connaisse) qui utilise une solution relativement (voir très) proche de celle présentée ici.<br>
Il utilise une clé PGP pour identifier un utilisateur et une DHT pour identifier les adresses IP des machines.<br>
La solution utilisée par RetroShare me semble bonne, elle est par contre dépendante de RetroShare.<br>
La solution que je propose reprend celle de RetroShare, mais sous un axe global et indépendant des applications.<br>
Il ne devrait pas être problématique de porter RetroShare sur PGPID.</p>
<p>Je vous invite à lire le très bon article de Stéphane Bortzmeyer (qui traine aussi ici) au sujet des url : <a href="http://www.bortzmeyer.org/no-free-lunch.html">http://www.bortzmeyer.org/no-free-lunch.html</a></p>
<h2 id="présentation">Présentation</h2>
<p>PGPID est donc une solution basée sur PGP, qui fournirait :</p>
<ul>
<li>un système entièrement a-centré d'identification et d'authentification des utilisateurs.</li>
<li>un système d'association de machines à un utilisateur.</li>
<li>un système bas niveau fournissant des services de base aux applications construites au-dessus.</li>
</ul><p>Je vous préviens tout de suite, PGPID n'est pas une solution complète :<br>
C'est une solution pour un réseau social au sens premier du terme. Il ne présume en rien des applications bâties dessus (même si j'en parle un peu).</p>
<p>C'est vous voulez un résumé en une phrase (et donc fausse) : PGPID est au réseau d'humain ce que IP/DNS est au réseau d'ordinateur.</p>
<p>Le principe de base est le suivant :</p>
<ul>
<li>L'utilisateur se crée une clé à chiffrement symétrique (PGP)</li>
<li>La clé publique sert d'identifiant.</li>
<li>Cet identifiant est mis dans une DHT.</li>
<li>À un identifiant est associé un certain nombre de métadonnées dans la DHT.</li>
<li>Pour éviter qu'un vilain pirate ne change vos données, l'ensemble des données est signé avec la clé associée.</li>
<li>Une donnée principale (si ce n'est pas la seule donnée) est une liste de nœuds.</li>
<li>Chacun de ces nœuds, lorsqu'ils sont interrogés, permettent de connaitre quel nœuds fournit quel services associés à la clé.</li>
</ul><h3 id="définitions">Définitions</h3>
<p>Bon, c'est très loin d'être une spec, mais il faut bien avoir des noms clairs pour savoir de quoi on parle.<br>
J'en conviens c'est un peu imbuvable à première lecture, mais n'arrêtez pas tous de suite, il y a des exemples après.</p>
<h4 id="service">Service</h4>
<p>Fonctionnalité apportée par une application (le plus souvent cliente).<br>
Un service peut être :</p>
<ul>
<li>Un fournisseur d'adresse de machine et des services associés.</li>
<li>Une application de messagerie instantanée.</li>
<li>Un serveur web.</li>
<li>Un service de notifications</li>
<li>Un service d'échange de fichiers</li>
<li>Un serveur mail.</li>
</ul><h4 id="sn-service-name">SN (Service Name)</h4>
<p>Le nom d'un service.</p>
<h4 id="pgpid">PGPID</h4>
<p>L'identifiant d'un compte. C'est le fingerprint de la clé PGP.</p>
<h4 id="pgpidc">PGPIDC</h4>
<p>PGPID content</p>
<p>La clé publique complète PGP.</p>
<h4 id="spn-ou-node">SPN (ou Node)</h4>
<p>Service Provider Node.</p>
<p>Une machine fournissant un ou plusieurs services.</p>
<h4 id="nds">NDS</h4>
<p>Node discovery service</p>
<p>Un service particulier qui permet d'obtenir une liste de SPN associé à une PGPID.</p>
<h4 id="ndn">NDN</h4>
<p>Node discovery node (Noeud d'aiguillage).</p>
<p>Un SPN fournissant NDS.<br>
Ce sont ces nœuds qui l'on met dans la DHT.<br>
Le NDS n'étant qu'un service particulier, le NDS du NDN doit inclure le NDN dans la liste.</p>
<h4 id="sds">SDS</h4>
<p>Service discovery service</p>
<p>Un service fournissant la liste des services associés à un PGPID fournit par le SPN.<br>
Chaque SPN doit avoir un SDS</p>
<h4 id="ts">TS</h4>
<p>Table service</p>
<p>Service fournissant la partie DHT (stockage/recherche des clés)</p>
<h4 id="tn">TN</h4>
<p>Table node</p>
<p>Un SPN fournissant TS</p>
<h4 id="fpgpid">FPGPID</h4>
<p>Full PGPID. Il est de la forme : <PGP fingerprint>[@<SPN>][:<SN>]<br>
Il permet d'identifier jusqu'à un service particulier d'une machine particulière d'un utilisateur.</p>
<h2 id="identification">Identification</h2>
<p>L'identifiant de l'utilisateur est le fingerprint de la clé PGP.</p>
<p>Chaque utilisateur peut potentiellement avoir plusieurs nœuds fournissant des services. Ces machines sont identifiées par : ID@SPN<br>
Ici, SPN est un nom choisi arbitrairement par l'utilisateur. Ça peut être «maison», «pc», «portable», …<br>
Ceci peut être comparé au ressource dans les xmppid.</p>
<p>Chaque service à un nom, (plus ou moins standardisé). C'est l'équivalent des ports.<br>
Ainsi un service mail pourra être adressé avec l'identifiant : ID@maison:mail<br>
De même un service de messagerie instantané sur téléphone sera adressé avec : ID@phone:im</p>
<h2 id="use-case">Use Case</h2>
<h3 id="inscription">Inscription</h3>
<p>Alice souhaite s'inscrire (si on peut appeler ça s'inscrire) sur PGPID.</p>
<ol>
<li>Elle lance son programme et crée une paire de clés PGP.</li>
<li>Elle contacte un TN connu et lui envoie sa PGPIDC.</li>
<li>Le TN se charge de diffuser sa PGPIDC dans la DHT.</li>
</ol><p>Alice a maintenant une «identité» sur PGPID</p>
<h3 id="identification-sur-les-sites-web"> Identification sur les sites web</h3>
<p>Son navigateur web étant configuré, lorsque Alice navigue sur un site supportant l'identification PGPID, elle est automatiquement identifiée.</p>
<ol>
<li>Échange d'information entre le navigateur et le serveur web pour s'informer qu'ils supportent l'identification PGPID</li>
<li>Le serveur envoie un défi au navigateur</li>
<li>Le navigateur chiffre le défi (avec éventuelle confirmation d'Alice)</li>
<li>Le serveur récupère le PGPIDC d'Alice en contactant un TN.</li>
<li>Le serveur vérifie le chiffrement du défi et Alice est authentifiée.</li>
</ol><p>Aucun mot de passe ne transite et n'est fourni à aucun serveur.</p>
<h3 id="participation-au-réseau-pgpid">Participation au réseau PGPID</h3>
<p>Alice souhaite participer au réseau en installant un TS sur sa machine.</p>
<ol>
<li>Elle installe un TS sur sa machine.</li>
<li>Elle contacte un TN et enregistre sa machine en tant que nouveau TN.</li>
<li>L'adresse IP:port du TS sont propagés dans la DHT.</li>
<li>Son TN commence à recevoir des PGPIDC à stocké et des requêtes de PGPID.</li>
</ol><h3 id="installer-et-fournir-de-nouveaux-services">Installer et fournir de nouveaux services</h3>
<p>Pour pouvoir fournir des services la machine d'Alice doit devenir un SPN</p>
<ol>
<li>Alice installe un SPS. Sa machine devient donc un SPN</li>
<li>Pour que son SPN puisse être contactée Alice doit enregistrer son SPN auprès d'un NDN.</li>
<li>Elle décide d'utiliser le NDN de «la Société À la Mode sur Internet»</li>
<li>Elle contacte donc le NDN et y enregistre son SPS (adresse IP du SPN et port du SPS)</li>
<li>Alice rajoute l'adresse IP du NDN et de port du NDS aux métadonnées associées à son PGPID, les signes et les envoie sur un TN connu (probablement le sien)</li>
<li>Son SDS est configuré pour indiquer que le SPN fourni les services SDS (obligatoirement) et TS (puisque c'est aussi un TN)</li>
</ol><h3 id="installer-un-service-de-messagerie-instantané-im">Installer un service de messagerie instantané (IM)</h3>
<p>1. Alice a installé un service de messagerie supportant le protocole PGPID<br>
1. Alice configure son SDS pour qu'il indique aussi que son SN fourni le service IM</p>
<h3 id="communiquer-avec-bob">Communiquer avec Bob</h3>
<ol>
<li>Alice rentre le PGPID de Bob dans son logiciel IM</li>
<li>son logiciel contacte un TN, demande le PGPIDC de Bob et la liste de NDN.</li>
<li>le logiciel contacte le(s) NDN et demande au NDS une liste de SPN correspondant au PGPID de Bob</li>
<li>le logiciel contacte le(s) SPN et demande au SDS la liste des services fournit.</li>
<li>si le service IM est dans la liste, on a trouvé le SPN fournissant le service IM</li>
<li>le logiciel d'Alice contacte le logiciel de Bob sur le bon SPN</li>
<li>Une authentification a lieu (défi, chiffrement, déchiffrement)</li>
<li>La communication commence</li>
</ol><h3 id="avoir-un-deuxième-spn">Avoir un deuxième SPN</h3>
<p>Alice a un portable, elle souhaite installer un logiciel IM pour discuter à partir de celui ci</p>
<ol>
<li>Alice installe un SPS sur son portable</li>
<li>Alice configure le NDS pour qu'il retourne aussi l'adresse de son portable.</li>
<li>Bob peut contacter Alice aux adresses :
<ul>
<li><Alice PGPID>. Le SPN contacté dépendra de l'ordre de priorité configuré dans le NDS.</li>
<li><Alice PGPID>@pc</li>
<li><Alice PGPID>@portable</li>
</ul>
</li>
</ol><h3 id="idées-de-service-possible">Idées de service possible</h3>
<ul>
<li>vcard. service fournissant des informations complémentaires sur l'utilisateur (adresse, avatar, …). Le service vcard (comme tous les autres services, y compris NDS et SDS) peut fournir des informations différentes selon le demandeur.</li>
<li>service web.</li>
<li>réseaux sociaux</li>
<li>échange de fichier</li>
<li>sauvegarde de données. PGPID1@pc peut envoyer des données à PGPID2@pc/backup. Permet de sauvegarder automatiquement des données chez d'autres personnes et vice versa (échange de bon procédés)</li>
<li>synchronisation de données entre les machines d'une même personne. (entre PGPID1@pc et PGPID1@portable)</li>
</ul><h2 id="utilisation-avancée">Utilisation avancée</h2>
<h3 id="avoir-plusieurs-ndn">Avoir plusieurs NDN</h3>
<p>Alice a aussi un téléphone portable. Elle souhaite l'utiliser, ainsi que son pc, comme NDN. Le téléphone servant de NDN de secours lorsque son pc est éteint.<br>
De plus elle souhaite aussi s'en servir pour faire de la IM.</p>
<p>Elle installe donc :</p>
<ul>
<li>sur son pc
<ul>
<li>un NDS qui liste son pc.</li>
<li>un SDS qui liste les services disponibles sur son pc</li>
</ul>
</li>
<li>sur son téléphone portable
<ul>
<li>un NDS qui liste son portable.</li>
<li>un SDS qui liste les services disponibles sur son portable (IM)</li>
</ul>
</li>
<li>dans la DHT
<ul>
<li>l'adresse de ses deux NDS, le NDS du pc ayant une plus grande priorité.</li>
</ul>
</li>
</ul><h3 id="itinérance">Itinérance</h3>
<p>Bob a un accès internet avec une adresse IP dynamique.<br>
Pour éviter que la DHT soit mise à jour trop souvent, Bob utilise un NDN ne lui appartenant pas qui lui a une adresse fixe.<br>
Celui-ci sert de relais vers son adresse dynamique.<br>
Son SPN contacte régulièrement les NDN qui le référencent pour qu'ils mettent à jour leurs informations. (équivalent des services dynDNS)<br>
C'est d’ailleurs la raison d'être des NDN (au lieu de mettre directement les SPN dans la DHT) : Avoir des SPN dynamiques et utiliser les NDN (fixe) qui servent «d'aiguillage». Le tout sans remettre à jour la DHT trop souvent.</p>
<h3 id="résilience-des-données">Résilience des données</h3>
<p>Alice a fait un blog. Pour cela elle utilise le service blog sur son pc.<br>
Son PC n'est pas toujours connecté mais Alice souhaiterais que ses informations soient toujours disponibles.<br>
Elle s'arrange avec Bob. Lorsque Alice écrit un article, elle le publie sur son pc mais aussi sur une machine de Bob.<br>
Elle configure son NDN pour qu'il retourne le SPN de Bob.<br>
Bob de son côté configure son SPN pour répondre aux requêtes au sujet d'Alice.<br>
Lorsque Charles veut lire les articles d'Alice il se contacte à son NDN. Si le SPN d'Alice est connecté il va lire l'article dessus.<br>
Sinon il se connecte au SPN de Bob pour accéder à l'article.<br>
Les données venant d'Alice devant être signé par la clé privée d'Alice et Bob ne pouvant en aucun cas y accéder (à la clé privée), il faut qu'Alice signe ses articles à l'avance.<br>
Il ne peut donc pas y avoir de contenue dynamique hébergé chez un tiers. (Sauf à mettre en place un protocole de délégation des signatures/réseau de confiance avancé)</p>
<p>Alice, Bob et Charles peuvent aussi se mettre d'accord pour louer un serveur et mettre en place un NDN/SPN commun.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Je pense que vous avez le compris le principe et je laisse votre imagination débordante trouver de nouveaux cas d'utilisation.</p>
<p>J'aimerais pourtant revenir sur quelques points :</p>
<ul>
<li>PGPID se veut neutre quant à son utilisation. Il est tout à fait possible de créer des services a-centrés comme des totalement centrés autour d'une seule entité. Dans les deux cas, l'utilisateur à un identifiant unique. Son réseau personnel et <s>ses</s> son identifiant restent identique, quel que soit son hébergeur du moment.</li>
<li>Pour fonctionner, un système de ce genre n'a pas besoin d'être très étendu et utilisé. Un bout de serveur toujours allumé dans un coin pour faire office de TN de référence. PGPID peut même être utilisé «en solo» par un seul utilisateur qui s'en servirait pour identifier ses machines.</li>
<li>Pour fonctionner à grande échelle (comprendre madame Michu), il faut que les outils soient le plus simple possible.
Il faut donc imaginer que :
<ul>
<li>Le TN de référence soit connu des outils, s'inscrire doit revenir à appuyer sur un bouton et rentrer son mot de passe.</li>
<li>Il soit possible de faire des recherches de personnes et ainsi retrouver ses contacts.</li>
<li>On utilise des QRCode et autres NFC. Il me suffit de scanner le QRcode d'un ami avec mon téléphone intelligent pour l'ajouter.</li>
</ul>
</li>
</ul><p>Quoi qu'il en soit, il faut voir ce qui est décrit plus au comme un draft. J'insiste sur le fait que c'est très loin d'être une spec (vous vous en serez rendu compte de vous-même). Beaucoup de choses peuvent changer et c'est en partie pour ça que j'en parle. Qu'est-ce que vous en pensez ? C'est l'idée ou la fausse bonne idée du siècle ? Je mérite le Turing Award ou la fosse commune ? Ou un peu entre les deux, voir les deux ?</p>
<h2 id="conclusion-bis-aka-vaporware">Conclusion bis (aka vaporware)</h2>
<p>Alors, c'est cool, vous voulez vous y mettre. Et vous vous demandez : "Quand est-ce que ça sort ?"</p>
<p>Et bien je vous répondrai : À moins que ma boite veuille copier google et m'offrir 20 % du temps pour des projets perso, ça mettra longtemps avant d'arriver.<br>
J'ai déjà d'autres projets perso en cours et, hélas, mon temps libre a des limites. J'ai bien écrit deux bouts de code en python qui s'échangent des clés PGP mais c'est loin d'être utilisable et je ne sais absolument pas quand ça le sera…</p>
<p>Ça y est, j'ai terminé. C'est à vous d'écrire maintenant : à vos commentaires !!</p><div><a href="https://linuxfr.org/users/mgautier/journaux/presentation-d-idee-pgpid.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/99169/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/mgautier/journaux/presentation-d-idee-pgpid#comments">ouvrir dans le navigateur</a>
</p>
GaMahttps://linuxfr.org/nodes/99169/comments.atom