[{"data":1,"prerenderedAt":17693},["ShallowReactive",2],{"post-woodstock_v2":3,"related-woodstock_v2":1778},{"id":4,"title":5,"author":6,"body":7,"category":1681,"categorySlug":1682,"date":1683,"description":13,"excerpt":1684,"extension":1763,"location":1764,"meta":1765,"navigation":1766,"path":1767,"published":1766,"seo":1768,"slug":1769,"stem":1770,"tags":1771,"timeToRead":1776,"__hash__":1777},"posts\u002Fposts\u002FWoodstock\u002F2026-04-26_woodstock_v2.md","Woodstock Backup v2.0.0 - La réécriture complète en Rust","Ulrich Vandenhekke",{"type":8,"value":9,"toc":1644},"minimark",[10,14,17,20,114,117,126,131,136,139,146,150,158,165,168,172,179,187,190,194,201,205,208,211,215,222,226,230,237,244,248,251,339,353,357,365,393,396,403,407,418,428,432,435,438,442,446,452,455,458,461,464,468,471,475,478,484,487,491,501,504,527,530,541,547,551,555,558,780,783,817,821,824,878,881,885,888,895,898,905,908,915,918,925,942,949,952,956,960,963,969,984,987,991,994,998,1001,1020,1571,1574,1577,1580,1586,1593,1599,1606,1617,1621,1624,1627,1641],[11,12,13],"p",{},"Bonjour à tous,",[11,15,16],{},"Six ans. Il m'aura fallu six ans entre la première version de Woodstock Backup et cette v2. Si vous m'aviez dit en 2020\nque je passerais la moitié de la décennie à réécrire trois fois le même logiciel de sauvegarde... j'aurais quand même\nfoncé tête baissée. C'est ma façon de faire. Me voilà donc avec une version 2 stable,\nentièrement réécrite en Rust, qui tourne en production sur ma petite infrastructure depuis plus d'un an. Et je suis\nvraiment content du résultat. 😄",[11,18,19],{},"Pour ceux qui me lisent depuis longtemps, voici un récapitulatif des articles qui ont précédé celui-ci :",[21,22,23,39],"table",{},[24,25,26],"thead",{},[27,28,29,33,36],"tr",{},[30,31,32],"th",{},"Article",[30,34,35],{},"Date",[30,37,38],{},"Sujet",[40,41,42,58,72,86,100],"tbody",{},[27,43,44,52,55],{},[45,46,47],"td",{},[48,49,51],"a",{"href":50},"\u002Fpost\u002Fwoodstock","Woodstock Backup v1.0.0",[45,53,54],{},"2020-09-20",[45,56,57],{},"Présentation du projet, prototype TypeScript + rsync",[27,59,60,66,69],{},[45,61,62],{},[48,63,65],{"href":64},"\u002Fpost\u002Fwoodstock_brtfs","Woodstock Backup - Btrfs",[45,67,68],{},"2021-01-12",[45,70,71],{},"Abandon de Btrfs, écriture d'un pool custom",[27,73,74,80,83],{},[45,75,76],{},[48,77,79],{"href":78},"\u002Fpost\u002Fwoodstock_protocol_language_sauvegarde","Woodstock Backup - Protocole et Langage de sauvegarde",[45,81,82],{},"2021-04-18",[45,84,85],{},"Protocole gRPC maison",[27,87,88,94,97],{},[45,89,90],{},[48,91,93],{"href":92},"\u002Fpost\u002Fwoodstock_rust","Woodstock Backup - Optimiser Node.js avec Rust",[45,95,96],{},"2023-05-10",[45,98,99],{},"NAPI-RS et bindings Rust pour réduire la consommation mémoire",[27,101,102,108,111],{},[45,103,104],{},[48,105,107],{"href":106},"\u002Fpost\u002Fpr_backuppc_pool","Woodstock Backup - Reverse engineering de BackupPC",[45,109,110],{},"2024-05-07",[45,112,113],{},"Migration du pool BackupPC vers Woodstock",[11,115,116],{},"Pour les nouveaux, je résume : Woodstock Backup est mon logiciel de sauvegarde personnel, centralisé, qui sauvegarde\ntoutes les machines de mon réseau local et mes serveurs distants sur un NAS. L'idée de départ était simple. Le résultat\nest... un peu plus complexe. :)",[11,118,119],{},[120,121],"img",{"alt":122,"src":123,"className":124},"Pool of chunks","\u002FWoodstock\u002Fv2_splash.png",[125],"img-center",[127,128,130],"h2",{"id":129},"le-long-chemin-de-2020-à-2026","Le long chemin de 2020 à 2026",[132,133,135],"h3",{"id":134},"prototype-1-typescript-rsync-btrfs-2020","Prototype 1 : TypeScript + rsync + Btrfs (2020)",[11,137,138],{},"Tout a commencé avec un prototype en TypeScript qui utilisait rsync pour copier les fichiers et Btrfs pour les\nsnapshots incrémentaux. L'idée était élégante sur le papier : rsync calcule le delta, Btrfs déduplique, tout le monde\nest content.",[11,140,141,142,145],{},"En pratique, je me suis rapidement retrouvé avec des problèmes de stabilité de Btrfs lorsque le nombre de snapshots\ndevient élevé et que le système de fichiers approche de la saturation. J'en ai ",[48,143,144],{"href":64},"parlé en détail dans mon article sur\nBtrfs",". En résumé : abandonné.",[132,147,149],{"id":148},"prototype-2-typescript-grpc-pool-custom-2021","Prototype 2 : TypeScript + gRPC + pool custom (2021)",[11,151,152,153,157],{},"L'abandon de Btrfs m'a forcé à écrire mon propre pool de stockage basé sur le principe de ",[154,155,156],"strong",{},"Content-Addressable\nStorage"," (CAS) : chaque bloc de fichier (chunk) est identifié par son hash, et si deux fichiers partagent des blocs\nidentiques, ces blocs ne sont stockés qu'une seule fois. La déduplication est donc native.",[11,159,160,161,164],{},"Pour transférer les fichiers entre les machines à sauvegarder et le serveur, j'ai abandonné rsync et développé ",[48,162,163],{"href":78},"mon\npropre protocole basé sur gRPC",". gRPC s'appuie sur HTTP\u002F2, offre une\ncompression et un TLS natifs, et les bibliothèques existent pour tous les langages. Parfait.",[11,166,167],{},"Ce prototype, toujours en TypeScript, fonctionnait. Mais le moteur JavaScript de Node.js a ses limites. En particulier,\nla représentation en mémoire des objets JavaScript est beaucoup plus gourmande que celle d'un langage compilé. Pour un\noutil qui doit gérer des millions de fichiers, c'est un vrai problème.",[132,169,171],{"id":170},"lère-des-bindings-rust-2023","L'ère des bindings Rust (2023)",[11,173,174,175,178],{},"L'idée de cette phase était séduisante : garder toute la partie cœur en Rust pour les performances, et conserver le\nTypeScript pour la couche GraphQL et la logique applicative. Rust pour ce qui est critique, TypeScript pour ce qui est\nlisible et rapide à écrire. C'est ce que j'ai ",[48,176,177],{"href":92},"décrit dans mon article de 2023",".",[11,180,181,182,186],{},"Bien sûr, je me trompais. En pratique, il fallait des bindings d'exposition NAPI-RS pour chaque interface entre les\ndeux langages, des DTOs côté TypeScript qui doublonnaient les structures Rust, et surtout, plus le temps passait, plus\nle cœur métier migrait vers Rust — laissant de moins en moins de code côté TypeScript. La complexité était réelle :\ngérer des équivalents d'",[183,184,185],"code",{},"Observable"," ou du streaming en passant par des bindings NAPI-RS n'est pas trivial, même si\nles dernières versions de NAPI-RS ont depuis apporté des solutions à ces problèmes.",[11,188,189],{},"Le tout en restant performant malgré le coût du passage par les bindings TypeScript. C'était jouable, mais c'était\nde la jonglerie.",[132,191,193],{"id":192},"la-migration-du-pool-backuppc-2024","La migration du pool BackupPC (2024)",[11,195,196,197,200],{},"En parallèle, j'ai travaillé sur la migration de mon pool BackupPC existant vers le format Woodstock. La première\napproche envisagée consistait à monter les sauvegardes BackupPC via FUSE et à les lire comme un système de fichiers\nnormal. J'ai finalement opté pour une approche plus directe : ",[48,198,199],{"href":106},"lire directement le format interne de BackupPC",",\nce qui impliquait un travail de rétro-ingénierie non négligeable. La migration a été un succès, et j'ai pu enfin\nabandonner complètement BackupPC.",[132,202,204],{"id":203},"la-décision-de-tout-réécrire-en-rust-2024","La décision de tout réécrire en Rust (2024)",[11,206,207],{},"À ce stade, la situation était devenue évidente : la partie TypeScript avait tellement rétréci qu'il ne restait plus\ngrand-chose dedans. Un serveur NestJS (framework Node.js) pour l'API, une interface web en Vue.js, et quelques couches\nde glue — le reste était déjà en Rust. Maintenir deux écosystèmes pour si peu de code TypeScript n'avait plus aucun\nsens.",[11,209,210],{},"J'ai donc décidé de sauter le pas : supprimer les bindings, réécrire intégralement le backend en Rust. Plus de\nNAPI-RS, plus de DTOs en double, plus de jonglerie entre deux compilateurs. Du Rust pur, de bout en bout.",[132,212,214],{"id":213},"woodstock-backup-v2-la-version-stable-2026","Woodstock Backup v2 : la version stable (2026)",[11,216,217,218,221],{},"La version ",[183,219,220],{},"2.0.0"," est en production depuis plus d'un an maintenant, et je la considère comme stable. Les sauvegardes tournent tous les jours, les restaurations\nfonctionnent, et je dors tranquille.",[127,223,225],{"id":224},"architecture-de-woodstock-backup-v2","Architecture de Woodstock Backup v2",[132,227,229],{"id":228},"vue-densemble-le-modèle-pull","Vue d'ensemble : le modèle Pull",[11,231,232,233,236],{},"L'architecture reste fondée sur le ",[154,234,235],{},"modèle pull"," : c'est le serveur de sauvegarde qui initie les connexions vers les\npériphériques, et non l'inverse. Cela offre une garantie de sécurité importante : un périphérique compromis ne peut\npas écrire de données arbitraires dans le serveur de sauvegarde.",[238,239,240],"center",{},[120,241],{"alt":242,"src":243},"Architecture Pull - Woodstock v2","https:\u002F\u002Fwww.plantuml.com\u002Fplantuml\u002Fpng\u002FVLJDRjD04BxxAOPm81M47hZbK9N-g19AfLAZ7b2aQB8UnzkiPvsTtRJSE25nHJm0Hy9h-4tw9E3ysaxAnPPs_hxvlfav5O_EXzn4Btn6EK6Edfp6A1hRH-Z4vEOK72G4Wc5E4tG9TU3bG4yoVsO2HG05Eg-LBf0zYCee2OPSw_tUZaSFratt3CfeOZ_2Ge-agjMsDmm9UXoZ47G-sE0OpP2Vlls0QsITadZg00hShqmDznjh3Po_ZuVSFJCufNUlFujFZfR-XRKc8avWR1_NNT-K2wUBhFhE0a7vgzQystH_vOYuXVP1Hkk64gJSSWD4p5X8Pdq5mhjKZk_YU0L1rfQc-nVnvU-SXfmGf5fbcfmitLFPuMNh2UoSN8qfw4DCpeCX0KUpKFxn970NwEsz3BbxUnb_EhvoM6GV1sz0a0Km-EmeYjhmeNUoBn3q8VyqYE7fwqyGFM4qb1Dxi6mqqx5Dq-eVHTjHgBBiz8S-N9GBPOXLHc2mHksGkqB6CXG6cJLFZY9KNi_HKts0Qhbw9tkKGn_EBJCzQiimkRqvNw8TSbUpzhfLiLPWJxf3P6o4geguSa4GUFlMa7MNTwljDhPt0gb07mQaV71KxPPvXMvi7OaYLhGBJYLA1NhDofl1WDfSH0dLWo9ZRG4tw9GDJY0XsNd2FcMzja83BPuQOT0LxmZpfIe0JGtMA_UFbVAxSdhLLTXk8d6oGMI30vLXjLKg2po577aMaFrUpWEwNb2ErJ8aOHLyi9K6LLkATn4x6PuPdcSpjqcwvBdLzTJD1ggxKgdbhPhYX20f5qaeZ9w5Sj6wG_yXT7tee77dLepM9DyEMUrjRw0FmhlMiZnmLSMg5qUfie4RcMgxTqgSXDpy1G00",[132,245,247],{"id":246},"les-composants-rust","Les composants Rust",[11,249,250],{},"L'ensemble de la solution est écrit en Rust. Côté serveur, quatre microservices ; côté client, un démon déployé sur\nchaque machine à sauvegarder :",[21,252,253,266],{},[24,254,255],{},[27,256,257,260,263],{},[30,258,259],{},"Composant",[30,261,262],{},"Déployé sur",[30,264,265],{},"Rôle",[40,267,268,281,297,312,324],{},[27,269,270,275,278],{},[45,271,272],{},[183,273,274],{},"api_server",[45,276,277],{},"Serveur",[45,279,280],{},"API REST\u002FGraphQL pour l'interface Vue.js — hors du chemin de sauvegarde",[27,282,283,288,290],{},[45,284,285],{},[183,286,287],{},"client_api_server",[45,289,277],{},[45,291,292,293,296],{},"Reçoit les connexions mTLS de ",[183,294,295],{},"ws_client_daemon"," pour le signalement online\u002Foffline",[27,298,299,304,306],{},[45,300,301],{},[183,302,303],{},"job_worker",[45,305,277],{},[45,307,308,309,311],{},"Exécute les sauvegardes : connexion gRPC vers ",[183,310,295],{},", transfert des chunks, déduplication",[27,313,314,319,321],{},[45,315,316],{},[183,317,318],{},"scheduler",[45,320,277],{},[45,322,323],{},"Gère le planning, déclenche les jobs selon les règles définies",[27,325,326,330,333],{},[45,327,328],{},[183,329,295],{},[45,331,332],{},"Chaque périphérique",[45,334,335,336,338],{},"Reçoit les connexions de ",[183,337,303],{},", crée les snapshots, envoie les chunks",[11,340,341,342,348,349,352],{},"La gestion des jobs de sauvegarde est assurée par ",[48,343,347],{"href":344,"rel":345},"https:\u002F\u002Fgithub.com\u002Fgeofmureithi\u002Fapalis",[346],"nofollow","Apalis",", qui s'appuie sur\n",[154,350,351],{},"Redis\u002FValkey"," comme backend de file d'attente. Apalis remplace ici BullMQ, qui remplissait ce rôle dans l'ancienne\nversion NestJS. C'est une architecture simple et fiable, qui permet également de distribuer les workers si un jour\nl'infrastructure venait à grossir.",[132,354,356],{"id":355},"le-pool-de-stockage-cas-blake3zstd","Le pool de stockage : CAS Blake3+Zstd",[11,358,359,360,364],{},"Le cœur du système est le pool CAS (",[361,362,363],"em",{},"Content-Addressable Storage","). Son fonctionnement est le suivant :",[366,367,368,376,383,386],"ol",{},[369,370,371,372,375],"li",{},"Chaque fichier est découpé en ",[154,373,374],{},"chunks"," de taille variable.",[369,377,378,379,382],{},"Chaque chunk est haché avec ",[154,380,381],{},"Blake3"," (un algorithme de hachage moderne, très rapide).",[369,384,385],{},"Si le hash du chunk est déjà présent dans le pool, il n'est pas retransféré ni re-stocké.",[369,387,388,389,392],{},"Si le chunk est nouveau, il est compressé avec ",[154,390,391],{},"Zstd"," avant d'être écrit sur disque.",[11,394,395],{},"La déduplication est donc réalisée au niveau des chunks, et non au niveau des fichiers entiers. Cela signifie que si\nun fichier de 10 Go n'a été modifié qu'à 1 %, seul 1 % sera retransféré et stocké.",[11,397,398,399,402],{},"Le pool maintient également un ",[154,400,401],{},"compteur de références"," (refcount) par chunk : quand une sauvegarde est supprimée,\nles chunks qui ne sont plus référencés sont supprimés du pool. Ce mécanisme de garbage collection permet de garder le\npool propre sans intervention manuelle.",[132,404,406],{"id":405},"sauvegardes-windows-vss-natif","Sauvegardes Windows : VSS natif",[11,408,409,410,413,414,417],{},"Depuis la version ",[183,411,412],{},"alpha.57",", le client Windows utilise le ",[154,415,416],{},"Volume Shadow Copy Service (VSS)"," de Windows pour créer\nun snapshot cohérent du système de fichiers avant la sauvegarde. Cela permet de sauvegarder des fichiers verrouillés\n(comme les bases de données, les fichiers de profil Outlook, etc.) sans erreur.",[11,419,420,421,423,424,427],{},"Plus besoin de rsync, de Cygwin, ou d'outils tiers : le client ",[183,422,295],{}," est un binaire Rust natif pour\nWindows, compilé avec la cible ",[183,425,426],{},"x86_64-pc-windows-msvc",", qui utilise les APIs Win32 directement. C'est\nnettement plus propre que l'ancienne approche.",[132,429,431],{"id":430},"sauvegardes-linux-snapshots-btrfs","Sauvegardes Linux : snapshots Btrfs",[11,433,434],{},"Sur les machines Linux dont le système de fichiers est Btrfs, le client crée un snapshot en lecture seule avant de\nlancer la sauvegarde. Cela garantit la cohérence des données sauvegardées, même si des fichiers sont modifiés pendant\nla sauvegarde.",[11,436,437],{},"Contrairement au premier prototype qui utilisait Btrfs côté serveur (ce qui causait les problèmes évoqués plus haut),\nici les snapshots sont créés côté client et uniquement pour la durée de la sauvegarde. C'est une utilisation beaucoup\nplus conservative de Btrfs.",[127,439,441],{"id":440},"les-défis-techniques","Les défis techniques",[132,443,445],{"id":444},"le-refcounting-un-problème-de-cohérence","Le refcounting : un problème de cohérence",[11,447,448,449,451],{},"Le plus grand défi de cette réécriture a été l'implémentation correcte du ",[154,450,401],{}," du pool CAS.",[11,453,454],{},"Le problème est le suivant : lorsqu'une sauvegarde est en cours, des chunks sont ajoutés au pool et leur refcount est\nincrémenté. Si la sauvegarde est interrompue (coupure réseau, arrêt du serveur, etc.), le pool peut se retrouver dans\nun état incohérent : des chunks présents dans le pool sans être référencés par aucune sauvegarde complète.",[11,456,457],{},"C'est la solution la plus complexe et qui a nécessité le plus de travail pour être implémentée de manière fiable. En\neffet, il faut s'assurer que le comptage de référence est bon si on ne veux pas se retrouver à éliminer des chunks encore référencés.",[11,459,460],{},"Afin de garantir que le comptage de référence est bon, un outil de récupération a été développé : il analyse le pool et les manifestes des sauvegardes, et corrige les refcounts en cas d'incohérence. Il n'est normallement nécessaire qu'en cas de\ncrash du serveur (coupure de courant, etc.) pendant une sauvegarde.",[11,462,463],{},"Actuellement la structure du pool repose sur le système de fichiers. L'inconvénient est à aujourd'hui la durée\nd'execution du fsck qui peut être très longue (taille du pool). En échange les opérations de lecture\u002Fécriture sont très\nrapides.",[132,465,467],{"id":466},"les-breaking-changes","Les BREAKING CHANGES",[11,469,470],{},"Par rapport à la version 1, on est sur une réécriture complète. Vu le peu de monde qui utilise cette version 1, il n'y a\npas de migration prévue. La version 2 est un nouveau projet, avec une nouvelle API, et des changements majeurs dans la façon dont les sauvegardes sont gérées.",[132,472,474],{"id":473},"windows-sans-rsync-binaire-natif-cross-compilé","Windows sans rsync : binaire natif cross-compilé",[11,476,477],{},"Dans la version 1, le client Windows était un serveur rsyncd couplé à Cygwin. C'était fonctionnel, mais peu élégant,\ndifficile à installer, et les permissions NTFS n'étaient pas correctement sauvegardées.",[11,479,480,481,483],{},"Dans la version 2, le client Windows est un binaire Rust compilé en cross-compilation depuis Linux avec la cible\n",[183,482,426],{}," et le linker LLVM\u002FClang. Le binaire est distribué seul, sans dépendance. Il se connecte au\nserveur de sauvegarde via gRPC mTLS, crée un snapshot VSS, parcourt le système de fichiers et envoie les chunks.",[11,485,486],{},"Les ACLs NTFS, les attributs étendus, les points de jonction et les liens symboliques Windows sont correctement\ngérés. C'est un vrai progrès.",[132,488,490],{"id":489},"la-sécurité-mtls-de-bout-en-bout","La sécurité : mTLS de bout en bout",[11,492,493,494,496,497,500],{},"Toutes les communications impliquant ",[183,495,295],{}," sont chiffrées et authentifiées par ",[154,498,499],{},"mutual TLS (mTLS)",".\nChaque périphérique possède un certificat client signé par une autorité de certification interne au serveur Woodstock.",[11,502,503],{},"Deux canaux mTLS distincts :",[505,506,507,517],"ul",{},[369,508,509,516],{},[154,510,511,513,514],{},[183,512,303],{}," ↔ ",[183,515,295],{}," (gRPC) : le canal de sauvegarde, à l'initiative du serveur.",[369,518,519,526],{},[154,520,521,523,524],{},[183,522,295],{}," → ",[183,525,287],{}," (REST) : le canal de présence, à l'initiative du client, qui lui permet\nde signaler son statut online\u002Foffline.",[11,528,529],{},"Cela garantit que :",[505,531,532,535,538],{},[369,533,534],{},"Les données sauvegardées sont chiffrées en transit.",[369,536,537],{},"Seul le serveur Woodstock peut déclencher une sauvegarde sur un périphérique enregistré.",[369,539,540],{},"Un périphérique ne peut pas usurper l'identité d'un autre.",[11,542,543,544,546],{},"À noter : ",[183,545,274],{},", qui sert l'interface Vue.js, ne dispose pas encore d'authentification. C'est prévu, mais pas encore fait (voir plus bas).",[127,548,550],{"id":549},"en-production-depuis-plus-dun-an","En production depuis plus d'un an",[132,552,554],{"id":553},"les-machines-sauvegardées","Les machines sauvegardées",[11,556,557],{},"Voici un tableau récapitulatif de mes neuf machines sauvegardées au 26 avril 2026 :",[21,559,560,581],{},[24,561,562],{},[27,563,564,567,569,572,575,578],{},[30,565,566],{},"Machine",[30,568,265],{},[30,570,571],{},"Sauvegardes",[30,573,574],{},"Fichiers",[30,576,577],{},"Taille brute",[30,579,580],{},"Compressé",[40,582,583,605,627,649,671,693,715,736,758],{},[27,584,585,590,593,596,599,602],{},[45,586,587],{},[183,588,589],{},"localhost",[45,591,592],{},"NAS local (Debian)",[45,594,595],{},"43",[45,597,598],{},"13 346",[45,600,601],{},"1,3 Go",[45,603,604],{},"0,8 Go",[27,606,607,612,615,618,621,624],{},[45,608,609],{},[183,610,611],{},"pc-eve",[45,613,614],{},"PC principal de la famille (Windows)",[45,616,617],{},"211",[45,619,620],{},"563 333",[45,622,623],{},"473 Go",[45,625,626],{},"413 Go",[27,628,629,634,637,640,643,646],{},[45,630,631],{},[183,632,633],{},"pc-m-eve",[45,635,636],{},"Ordinateur portable familial (Windows)",[45,638,639],{},"26",[45,641,642],{},"146 243",[45,644,645],{},"21,6 Go",[45,647,648],{},"13,5 Go",[27,650,651,656,659,662,665,668],{},[45,652,653],{},[183,654,655],{},"pc-m-ulrich",[45,657,658],{},"Mon portable personnel (Linux)",[45,660,661],{},"53",[45,663,664],{},"43 185",[45,666,667],{},"125 Go",[45,669,670],{},"64,5 Go",[27,672,673,678,681,684,687,690],{},[45,674,675],{},[183,676,677],{},"pc-ulrich",[45,679,680],{},"Mon PC de bureau (Linux)",[45,682,683],{},"322",[45,685,686],{},"1 385 001",[45,688,689],{},"108 Go",[45,691,692],{},"76,7 Go",[27,694,695,700,703,706,709,712],{},[45,696,697],{},[183,698,699],{},"pc-alex-linux",[45,701,702],{},"PC Linux secondaire",[45,704,705],{},"4",[45,707,708],{},"210 006",[45,710,711],{},"25 Go",[45,713,714],{},"14,5 Go",[27,716,717,722,725,728,731,734],{},[45,718,719],{},[183,720,721],{},"pc-alex-windows",[45,723,724],{},"PC Windows secondaire",[45,726,727],{},"360",[45,729,730],{},"429 368",[45,732,733],{},"147 Go",[45,735,689],{},[27,737,738,743,746,749,752,755],{},[45,739,740],{},[183,741,742],{},"server",[45,744,745],{},"Serveur dédié OVH principal",[45,747,748],{},"450",[45,750,751],{},"243 074",[45,753,754],{},"810 Go",[45,756,757],{},"752 Go",[27,759,760,765,768,771,774,777],{},[45,761,762],{},[183,763,764],{},"server-ovh-6",[45,766,767],{},"Second serveur OVH",[45,769,770],{},"472",[45,772,773],{},"493 441",[45,775,776],{},"194 Go",[45,778,779],{},"173 Go",[11,781,782],{},"Quelques observations :",[505,784,785,797,804],{},[369,786,787,791,792,796],{},[154,788,789],{},[183,790,742],{}," et ",[154,793,794],{},[183,795,764],{}," ont le plus grand nombre de sauvegardes (450 et 472). Ce sont des serveurs qui\ntournent 24\u002F7, avec des données critiques.",[369,798,799,803],{},[154,800,801],{},[183,802,677],{}," cumule plus de 1,3 million de fichiers sauvegardés, ce qui en fait la machine avec le plus grand\nnombre de fichiers individuels. Mon répertoire de développement est manifestement très fragmenté. 😄",[369,805,806,807,811,812,816],{},"La compression Zstd est particulièrement efficace sur ",[154,808,809],{},[183,810,655],{}," (49 % d'économie) et beaucoup moins\nsur ",[154,813,814],{},[183,815,742],{}," (7 %), ce qui s'explique par la nature des données stockées : données de développement vs.\ndonnées déjà compressées (archives, images Docker, etc.).",[132,818,820],{"id":819},"le-pool-global","Le pool global",[11,822,823],{},"Le pool CAS central agrège toutes les sauvegardes :",[21,825,826,836],{},[24,827,828],{},[27,829,830,833],{},[30,831,832],{},"Statistique",[30,834,835],{},"Valeur",[40,837,838,846,854,862,870],{},[27,839,840,843],{},[45,841,842],{},"Chunks uniques",[45,844,845],{},"3 365 564",[27,847,848,851],{},[45,849,850],{},"Références totales",[45,852,853],{},"60 888 193",[27,855,856,859],{},[45,857,858],{},"Espace pool compressé",[45,860,861],{},"1,95 To",[27,863,864,867],{},[45,865,866],{},"Espace disque total",[45,868,869],{},"9,6 To",[27,871,872,875],{},[45,873,874],{},"Espace disque utilisé",[45,876,877],{},"6,4 To",[11,879,880],{},"Le ratio références\u002Fchunks (60,9 M \u002F 3,4 M ≈ 18x) illustre l'efficacité de la déduplication : chaque chunk unique est\nen moyenne référencé 18 fois par différentes sauvegardes. L'historique long des sauvegardes explique cette valeur\nélevée.",[132,882,884],{"id":883},"interface-web","Interface web",[11,886,887],{},"L'interface web Vue.js 3 \u002F Vuetify permet de visualiser l'état de l'infrastructure, du pool, et de l'historique des\nsauvegardes de chaque machine.",[11,889,890],{},[120,891],{"alt":892,"src":893,"className":894},"Page Devices - liste des machines sauvegardées","\u002FWoodstock\u002Fv2_devices.png",[125],[11,896,897],{},"La page principale liste les neuf machines avec leur état, le nombre de sauvegardes, la taille brute et compressée.",[11,899,900],{},[120,901],{"alt":902,"src":903,"className":904},"Page Pool - statistiques du pool CAS","\u002FWoodstock\u002Fv2_pool.png",[125],[11,906,907],{},"La page pool affiche les statistiques globales du stockage : occupation disque, nombre de chunks, nombre de\nréférences, et l'évolution depuis le mois précédent.",[11,909,910],{},[120,911],{"alt":912,"src":913,"className":914},"Page Backups - détail des sauvegardes d'une machine","\u002FWoodstock\u002Fv2_host_detail.png",[125],[11,916,917],{},"La page de détail d'une machine liste l'historique complet de ses sauvegardes avec la date, la durée, et la liste des\npartitions sauvegardées.",[11,919,920],{},[120,921],{"alt":922,"src":923,"className":924},"Liste des sauvegardes avec état, durée et politique de rétention","\u002FWoodstock\u002Fv2_backup_list.png",[125],[11,926,927,928,931,932,931,935,938,939,178],{},"Chaque entrée de la liste indique le numéro de sauvegarde, la date de démarrage, la durée, le nombre de fichiers\ntotaux et nouveaux, les fichiers modifiés et supprimés, le compteur d'erreurs, et la politique de rétention\nappliquée : ",[154,929,930],{},"Horaire",", ",[154,933,934],{},"Quotidien",[154,936,937],{},"Hebdo"," ou ",[154,940,941],{},"Mensuel",[11,943,944],{},[120,945],{"alt":946,"src":947,"className":948},"Navigation dans les fichiers d'une sauvegarde","\u002FWoodstock\u002Fv2_backup_browse.png",[125],[11,950,951],{},"En cliquant sur une sauvegarde, on accède à la vue détaillée : statistiques complètes (fichiers, tailles, durée,\nvitesse), partitions sauvegardées avec leur type (Btrfs ou VSS), et un explorateur de fichiers permettant de\nnaviguer dans l'arborescence et de télécharger ou restaurer individuellement n'importe quel fichier.",[127,953,955],{"id":954},"perspectives","Perspectives",[132,957,959],{"id":958},"archivage-hors-site-sur-disque-usb","Archivage hors-site sur disque USB",[11,961,962],{},"Une fonctionnalité que je veux mettre en place depuis la v1 est l'archivage de la dernière version des sauvegardes\nsur un disque dur USB, qui est ensuite stocké hors-site. L'idée est d'avoir une copie physique des données dans un\nautre lieu en cas de sinistre (incendie, vol, etc.).",[11,964,965,966,178],{},"Dans la version 1 avec BackupPC, j'avais un script qui utilisait le connecteur FUSE de BackupPC pour monter les\nsauvegardes et les synchroniser avec rsync vers le disque USB. Ce script posait des problèmes avec les gros fichiers\net les permissions Windows, comme je l'avais mentionné ",[48,967,968],{"href":50},"dans le tout premier article",[11,970,971,972,975,976,979,980,983],{},"Dans Woodstock v2, l'outil en ligne de commande ",[183,973,974],{},"ws_console"," dispose d'une commande ",[183,977,978],{},"mount"," qui permet de monter\nune sauvegarde comme un système de fichiers FUSE. Il sera donc possible de faire un ",[183,981,982],{},"rsync"," depuis ce point de\nmontage vers le disque USB, avec une gestion correcte des permissions et des gros fichiers.",[11,985,986],{},"Ce n'est pas encore automatisé, mais c'est la prochaine chose que je veux mettre en place.",[132,988,990],{"id":989},"ajout-dun-format-de-stockage","Ajout d'un format de stockage",[11,992,993],{},"J'envisage également de pouvoir stocker directement le pool sur un bucket S3 ou compatible (SeaweedFS, RustFS). Avant de me lancer dans cette implémentation, je veux d'abord tester\nles performances d'un tel choix.",[127,995,997],{"id":996},"comparaison-avec-les-solutions-existantes","Comparaison avec les solutions existantes",[11,999,1000],{},"Avant de conclure, voici une comparaison honnête avec les alternatives réalistes. Si un autre outil correspond mieux à vos besoins, utilisez-le. Sans rancune.",[11,1002,1003,1004,931,1007,931,1010,931,1013,791,1016,1019],{},"Les outils couverts : ",[154,1005,1006],{},"Restic",[154,1008,1009],{},"BorgBackup",[154,1011,1012],{},"BackupPC",[154,1014,1015],{},"URBackup",[154,1017,1018],{},"Kopia",". Ils représentent les principales solutions open-source pour une infrastructure self-hostée multi-machines — ce qui correspond grosso modo au problème que Woodstock cherche à résoudre.",[21,1021,1022,1042],{},[24,1023,1024],{},[27,1025,1026,1029,1032,1034,1036,1038,1040],{},[30,1027,1028],{},"Critère",[30,1030,1031],{},"Woodstock v2",[30,1033,1006],{},[30,1035,1009],{},[30,1037,1012],{},[30,1039,1015],{},[30,1041,1018],{},[40,1043,1044,1068,1093,1116,1141,1165,1189,1213,1237,1260,1284,1308,1332,1357,1382,1407,1426,1446,1468,1489,1509,1529,1548],{},[27,1045,1046,1051,1054,1057,1060,1063,1066],{},[45,1047,1048],{},[154,1049,1050],{},"Langage",[45,1052,1053],{},"Rust",[45,1055,1056],{},"Go",[45,1058,1059],{},"Python + C",[45,1061,1062],{},"Perl",[45,1064,1065],{},"C++",[45,1067,1056],{},[27,1069,1070,1075,1078,1081,1084,1087,1090],{},[45,1071,1072],{},[154,1073,1074],{},"Licence",[45,1076,1077],{},"MIT",[45,1079,1080],{},"BSD-2",[45,1082,1083],{},"BSD",[45,1085,1086],{},"GPL v2+",[45,1088,1089],{},"AGPLv3+",[45,1091,1092],{},"Apache 2.0",[27,1094,1095,1100,1103,1106,1109,1112,1114],{},[45,1096,1097],{},[154,1098,1099],{},"Développement actif",[45,1101,1102],{},"✅ 2026",[45,1104,1105],{},"✅ 2025",[45,1107,1108],{},"✅ 2024",[45,1110,1111],{},"❌ 2020¹",[45,1113,1102],{},[45,1115,1105],{},[27,1117,1118,1123,1126,1129,1132,1135,1138],{},[45,1119,1120],{},[154,1121,1122],{},"Modèle de synchronisation",[45,1124,1125],{},"Pull (initié par le serveur)",[45,1127,1128],{},"Push (le client pousse)",[45,1130,1131],{},"Push (SSH ou local)",[45,1133,1134],{},"Pull (rsync\u002Ftar\u002FSMB)",[45,1136,1137],{},"Pull-like (découverte LAN)",[45,1139,1140],{},"Push \u002F mode serveur",[27,1142,1143,1148,1151,1154,1157,1160,1162],{},[45,1144,1145],{},[154,1146,1147],{},"Agent requis sur le client",[45,1149,1150],{},"✅ daemon",[45,1152,1153],{},"❌ binaire unique",[45,1155,1156],{},"❌ accès SSH",[45,1158,1159],{},"❌ rsync\u002FSMB",[45,1161,1150],{},[45,1163,1164],{},"❌ \u002F ✅ serveur optionnel",[27,1166,1167,1172,1175,1178,1181,1184,1186],{},[45,1168,1169],{},[154,1170,1171],{},"Planificateur intégré",[45,1173,1174],{},"✅ (Apalis + Redis)",[45,1176,1177],{},"❌ (cron\u002Fsystemd)",[45,1179,1180],{},"❌ (cron\u002FBorgmatic)",[45,1182,1183],{},"✅",[45,1185,1183],{},[45,1187,1188],{},"✅ (mode serveur)",[27,1190,1191,1196,1199,1202,1205,1208,1210],{},[45,1192,1193],{},[154,1194,1195],{},"Backends cloud\u002Fdistants",[45,1197,1198],{},"❌ self-hosted uniquement",[45,1200,1201],{},"✅ S3, B2, SFTP, Azure, GCS, rclone…",[45,1203,1204],{},"⚠️ SSH \u002F BorgBase",[45,1206,1207],{},"❌ disque local uniquement",[45,1209,1198],{},[45,1211,1212],{},"✅ S3, Azure, GCS, B2, SFTP, rclone…",[27,1214,1215,1220,1223,1226,1229,1232,1235],{},[45,1216,1217],{},[154,1218,1219],{},"Format de stockage",[45,1221,1222],{},"Pool CAS, fichiers sur disque",[45,1224,1225],{},"Pack files CAS",[45,1227,1228],{},"Journal de segments + index",[45,1230,1231],{},"Pool MD5 (niveau fichier) + reverse deltas",[45,1233,1234],{},"Snapshots de fichiers + images de blocs",[45,1236,1225],{},[27,1238,1239,1244,1247,1249,1252,1255,1258],{},[45,1240,1241],{},[154,1242,1243],{},"Granularité de déduplication",[45,1245,1246],{},"Chunk (CDC)",[45,1248,1246],{},[45,1250,1251],{},"Chunk (BUZHASH CDC)",[45,1253,1254],{},"Fichier (MD5 fichier complet, sans hardlinks en v4)",[45,1256,1257],{},"Fichier",[45,1259,1246],{},[27,1261,1262,1267,1270,1273,1276,1279,1282],{},[45,1263,1264],{},[154,1265,1266],{},"Dédup cross-machines",[45,1268,1269],{},"✅ (un pool partagé)",[45,1271,1272],{},"⚠️ uniquement si dépôt partagé",[45,1274,1275],{},"⚠️ uniquement au sein d'un dépôt",[45,1277,1278],{},"✅ (pool MD5 partagé)",[45,1280,1281],{},"✅ (niveau fichier)",[45,1283,1275],{},[27,1285,1286,1291,1293,1296,1299,1302,1305],{},[45,1287,1288],{},[154,1289,1290],{},"Compression",[45,1292,391],{},[45,1294,1295],{},"Zstd (depuis 0.14)",[45,1297,1298],{},"lz4, zstd, zlib, lzma",[45,1300,1301],{},"gzip \u002F bzip2",[45,1303,1304],{},"lzo \u002F zstd (optionnel)",[45,1306,1307],{},"zstd, lz4, gzip…",[27,1309,1310,1315,1318,1321,1324,1326,1329],{},[45,1311,1312],{},[154,1313,1314],{},"Chiffrement au repos",[45,1316,1317],{},"❌ pool en clair",[45,1319,1320],{},"✅ AES-256-CTR + Poly1305 (obligatoire)",[45,1322,1323],{},"✅ AES-256-CTR + HMAC (optionnel)",[45,1325,1317],{},[45,1327,1328],{},"⚠️ optionnel",[45,1330,1331],{},"✅ AES-256-GCM ou ChaCha20 (optionnel)",[27,1333,1334,1339,1342,1345,1348,1351,1354],{},[45,1335,1336],{},[154,1337,1338],{},"Chiffrement en transit",[45,1340,1341],{},"✅ mTLS (gRPC + REST)",[45,1343,1344],{},"⚠️ dépend du backend",[45,1346,1347],{},"⚠️ SSH ou local",[45,1349,1350],{},"⚠️ SSH ou SMB (optionnel)",[45,1352,1353],{},"⚠️ TLS optionnel",[45,1355,1356],{},"⚠️ TLS en mode serveur (optionnel)",[27,1358,1359,1364,1367,1370,1373,1376,1379],{},[45,1360,1361],{},[154,1362,1363],{},"Vérification d'intégrité",[45,1365,1366],{},"Hash Blake3 par chunk",[45,1368,1369],{},"SHA-256 par blob",[45,1371,1372],{},"HMAC-SHA256 \u002F BLAKE2b",[45,1374,1375],{},"MD5 fichier complet (v4+)",[45,1377,1378],{},"Hash",[45,1380,1381],{},"BLAKE2B ou SHA256",[27,1383,1384,1389,1392,1395,1398,1401,1404],{},[45,1385,1386],{},[154,1387,1388],{},"Modèle d'authentification",[45,1390,1391],{},"mTLS par appareil (CA interne)",[45,1393,1394],{},"Mot de passe + fichiers clés",[45,1396,1397],{},"Clés SSH + passphrase",[45,1399,1400],{},"Clés SSH",[45,1402,1403],{},"Certificats clients optionnels",[45,1405,1406],{},"Mot de passe + TLS optionnel",[27,1408,1409,1414,1416,1418,1420,1422,1424],{},[45,1410,1411],{},[154,1412,1413],{},"Linux",[45,1415,1183],{},[45,1417,1183],{},[45,1419,1183],{},[45,1421,1183],{},[45,1423,1183],{},[45,1425,1183],{},[27,1427,1428,1433,1436,1438,1440,1442,1444],{},[45,1429,1430],{},[154,1431,1432],{},"macOS",[45,1434,1435],{},"❌²",[45,1437,1183],{},[45,1439,1183],{},[45,1441,1183],{},[45,1443,1183],{},[45,1445,1183],{},[27,1447,1448,1453,1456,1458,1461,1464,1466],{},[45,1449,1450],{},[154,1451,1452],{},"Windows (natif)",[45,1454,1455],{},"✅ (binaire MSVC)",[45,1457,1183],{},[45,1459,1460],{},"❌ (WSL2 uniquement)",[45,1462,1463],{},"⚠️ (rsync\u002FCygwin ou SMB)",[45,1465,1183],{},[45,1467,1183],{},[27,1469,1470,1475,1477,1480,1483,1485,1487],{},[45,1471,1472],{},[154,1473,1474],{},"Snapshots VSS (Windows)",[45,1476,1183],{},[45,1478,1479],{},"✅ (depuis 0.12)",[45,1481,1482],{},"❌",[45,1484,1482],{},[45,1486,1183],{},[45,1488,1183],{},[27,1490,1491,1496,1498,1501,1503,1505,1507],{},[45,1492,1493],{},[154,1494,1495],{},"Snapshots Btrfs (Linux)",[45,1497,1183],{},[45,1499,1500],{},"❌ (hooks manuels)",[45,1502,1500],{},[45,1504,1482],{},[45,1506,1482],{},[45,1508,1482],{},[27,1510,1511,1516,1518,1520,1522,1524,1527],{},[45,1512,1513],{},[154,1514,1515],{},"Sauvegarde image \u002F bare-metal",[45,1517,1482],{},[45,1519,1482],{},[45,1521,1482],{},[45,1523,1482],{},[45,1525,1526],{},"✅ (Windows + Linux)",[45,1528,1482],{},[27,1530,1531,1536,1538,1540,1542,1544,1546],{},[45,1532,1533],{},[154,1534,1535],{},"Montage FUSE",[45,1537,1183],{},[45,1539,1183],{},[45,1541,1183],{},[45,1543,1482],{},[45,1545,1482],{},[45,1547,1183],{},[27,1549,1550,1554,1557,1560,1563,1566,1568],{},[45,1551,1552],{},[154,1553,884],{},[45,1555,1556],{},"✅ (sans auth pour l'instant)",[45,1558,1559],{},"❌ (tiers : Restic Browser)",[45,1561,1562],{},"❌ (tiers : Vorta)",[45,1564,1565],{},"✅ (CGI\u002FPerl)",[45,1567,1183],{},[45,1569,1570],{},"✅ (KopiaUI)",[11,1572,1573],{},"¹ La dernière version de BackupPC date de juin 2020. La base de code est stable mais n'est plus maintenue.",[11,1575,1576],{},"² La prise en charge de macOS n'est pas encore implémentée dans Woodstock.",[11,1578,1579],{},"Quelques points à souligner :",[11,1581,1582,1583,1585],{},"Le ",[154,1584,235],{}," (Woodstock, BackupPC, URBackup) signifie qu'un client compromis ne peut pas écrire de données arbitraires sur le serveur de sauvegarde. Le modèle push (Restic, Borg, Kopia) est plus simple à mettre en place, mais accorde davantage de confiance à chaque client.",[11,1587,1588,1589,1592],{},"La ",[154,1590,1591],{},"déduplication au niveau des chunks"," signifie que si un fichier de 10 Go est modifié d'1 Ko, seul cet 1 Ko est transféré et stocké. La déduplication au niveau fichier (BackupPC, URBackup) signifie que si un fichier change, le nouveau fichier entier est stocké dans le pool. Pour BackupPC spécifiquement, rsync gère le transfert de manière efficiente — seuls les blocs modifiés transitent sur le réseau — mais comme la correspondance dans le pool est basée sur un MD5 du fichier complet, un fichier modifié génère toujours une nouvelle entrée complète dans le pool. Pour les gros fichiers mutables — bases de données, disques de machines virtuelles, fichiers PST Outlook — la différence d'efficacité de stockage est considérable.",[11,1594,1588,1595,1598],{},[154,1596,1597],{},"déduplication cross-machines"," est moins courante. Si dix machines possèdent chacune une copie du même installeur de 500 Mo, Woodstock ne le stocke qu'une seule fois. Restic et Borg ne dédupliquent qu'au sein d'un même dépôt — pour obtenir une déduplication cross-machines, il faudrait mettre toutes les machines dans le même dépôt, ce qui crée des problèmes de contention de verrous et de contrôle d'accès.",[11,1600,1601,1602,1605],{},"La principale faiblesse de Woodstock : ",[154,1603,1604],{},"le pool n'est pas chiffré au repos",". Si quelqu'un obtient un accès physique ou système à votre NAS, il peut lire vos données de sauvegarde directement. L'approche de Restic — tout chiffrer, de manière obligatoire — est clairement plus solide si quelqu'un met la main sur votre disque. Sur un NAS de LAN privé que vous contrôlez physiquement, c'est un compromis que j'accepte. Pour un stockage distant ou dans le cloud, c'est une vraie limitation. Il est possible de contrer ce problème en chiffrant le disque lui-même (LUKS, BitLocker, etc.), mais c'est une couche supplémentaire à gérer.",[11,1607,1608,1609,1612,1613,1616],{},"Si vous avez besoin d'une ",[154,1610,1611],{},"restauration bare-metal",", URBackup est le seul outil de cette liste qui le fait nativement. Si vous avez besoin de ",[154,1614,1615],{},"BorgBackup sur Windows",", c'est impossible sans WSL2.",[127,1618,1620],{"id":1619},"conclusion","Conclusion",[11,1622,1623],{},"Ce projet m'a appris énormément de choses : Rust, gRPC, mTLS, la gestion de pools CAS, le VSS Windows, les snapshots\nBtrfs, la gestion de jobs distribués avec Redis, et probablement une dizaine d'autres sujets que j'ai oubliés depuis.",[11,1625,1626],{},"Est-ce que c'était raisonnable de passer six ans à construire son propre logiciel de sauvegarde alors qu'il en existe\ndes dizaines ? Bien sur que oui 😄.",[11,1628,1629,1630,1635,1636,178],{},"Le logiciel est disponible sur ",[48,1631,1634],{"href":1632,"rel":1633},"https:\u002F\u002Fgogs.shadoware.org\u002FShadowareOrg\u002Fwoodstock-backup",[346],"mon instance Gitea"," et la\ndocumentation est sur ",[48,1637,1640],{"href":1638,"rel":1639},"https:\u002F\u002Fwoodstock.shadoware.org",[346],"woodstock.shadoware.org",[11,1642,1643],{},"À bientôt !",{"title":1645,"searchDepth":1646,"depth":1646,"links":1647},"",2,[1648,1657,1664,1670,1675,1679,1680],{"id":129,"depth":1646,"text":130,"children":1649},[1650,1652,1653,1654,1655,1656],{"id":134,"depth":1651,"text":135},3,{"id":148,"depth":1651,"text":149},{"id":170,"depth":1651,"text":171},{"id":192,"depth":1651,"text":193},{"id":203,"depth":1651,"text":204},{"id":213,"depth":1651,"text":214},{"id":224,"depth":1646,"text":225,"children":1658},[1659,1660,1661,1662,1663],{"id":228,"depth":1651,"text":229},{"id":246,"depth":1651,"text":247},{"id":355,"depth":1651,"text":356},{"id":405,"depth":1651,"text":406},{"id":430,"depth":1651,"text":431},{"id":440,"depth":1646,"text":441,"children":1665},[1666,1667,1668,1669],{"id":444,"depth":1651,"text":445},{"id":466,"depth":1651,"text":467},{"id":473,"depth":1651,"text":474},{"id":489,"depth":1651,"text":490},{"id":549,"depth":1646,"text":550,"children":1671},[1672,1673,1674],{"id":553,"depth":1651,"text":554},{"id":819,"depth":1651,"text":820},{"id":883,"depth":1651,"text":884},{"id":954,"depth":1646,"text":955,"children":1676},[1677,1678],{"id":958,"depth":1651,"text":959},{"id":989,"depth":1651,"text":990},{"id":996,"depth":1646,"text":997},{"id":1619,"depth":1646,"text":1620},"Woodstock","woodstock","2026-04-26",{"type":8,"value":1685},[1686,1688,1690,1692,1756,1758],[11,1687,13],{},[11,1689,16],{},[11,1691,19],{},[21,1693,1694,1704],{},[24,1695,1696],{},[27,1697,1698,1700,1702],{},[30,1699,32],{},[30,1701,35],{},[30,1703,38],{},[40,1705,1706,1716,1726,1736,1746],{},[27,1707,1708,1712,1714],{},[45,1709,1710],{},[48,1711,51],{"href":50},[45,1713,54],{},[45,1715,57],{},[27,1717,1718,1722,1724],{},[45,1719,1720],{},[48,1721,65],{"href":64},[45,1723,68],{},[45,1725,71],{},[27,1727,1728,1732,1734],{},[45,1729,1730],{},[48,1731,79],{"href":78},[45,1733,82],{},[45,1735,85],{},[27,1737,1738,1742,1744],{},[45,1739,1740],{},[48,1741,93],{"href":92},[45,1743,96],{},[45,1745,99],{},[27,1747,1748,1752,1754],{},[45,1749,1750],{},[48,1751,107],{"href":106},[45,1753,110],{},[45,1755,113],{},[11,1757,116],{},[11,1759,1760],{},[120,1761],{"alt":122,"src":123,"className":1762},[125],"md","Lille, France",{"planet":1766},true,"\u002Fpost\u002Fwoodstock_v2",{"title":5,"description":13},"woodstock_v2","posts\u002FWoodstock\u002F2026-04-26_woodstock_v2",[1682,1772,1773,1774,1775],"backup","sauvegarde","rust","grpc",22,"gu0ISxCDyWPA2zrYBtgiI1k0uw4o8BK0cd-5g09z838",[1779,6081,13590,15725,16919],{"id":1780,"title":107,"author":6,"body":1781,"category":6054,"categorySlug":6055,"date":110,"description":6056,"excerpt":6057,"extension":1763,"location":1764,"meta":6073,"navigation":1766,"path":106,"published":1766,"seo":6074,"slug":6075,"stem":6076,"tags":6077,"timeToRead":3278,"__hash__":6080},"posts\u002Fposts\u002FWoodstock\u002F2024-03-14_pr_backuppc_pool.md",{"type":8,"value":1782,"toc":6036},[1783,1792,1795,1798,1804,1808,1811,1820,1828,1837,1845,1848,1851,1854,1857,1860,1863,1866,1870,1876,1884,1900,1909,1913,1916,1982,1986,1989,1998,2001,2005,2021,2028,2036,2039,2048,2051,2058,2061,2068,2071,2074,2078,2084,2087,2090,2095,2098,2102,2115,2121,2354,2357,2366,2372,2381,2384,2387,2667,2685,2693,2718,2985,2988,3640,3662,3668,3682,4152,4158,4347,4351,4354,4357,4372,4743,4750,4757,4760,4764,4767,4786,4794,4797,4801,4804,4812,4815,4818,4832,4841,4852,4912,4915,4986,4989,5277,5287,5294,5426,5433,5580,5589,5592,5599,5602,5609,5612,5798,5801,5805,5808,5817,5820,5824,5831,5841,5964,5970,5973,5984,5987,6003,6007,6010,6018,6024,6027,6029,6032],[11,1784,1785,1786,1791],{},"Une partie de cet article a été publiée sur ",[48,1787,1790],{"href":1788,"rel":1789},"https:\u002F\u002Flinuxfr.org\u002Fusers\u002Fsan_guickoo\u002Fjournaux\u002Fpullrequest-d-une-application-en-rust",[346],"LinuxFR",".\nAprès avoir reçu quelques retours, j'ai décidé de publier une version modifiée et améliorée de cet article sur mon blog.",[11,1793,1794],{},"Je remercie donc la communauté de LinuxFR pour ses retours. 😄",[11,1796,1797],{},"Les commentaires sur le code ou sur l'article sont les bienvenus.",[11,1799,1800],{},[120,1801],{"src":1802,"alt":1645,"className":1803},"\u002FWoodstock\u002Fhdd_unsplash.jpg",[125],[127,1805,1807],{"id":1806},"le-commencement","Le commencement",[11,1809,1810],{},"Actuellement, j'utilise BackupPC pour sauvegarder mes données. BackupPC est un logiciel de sauvegarde qui se connecte à\ndifférents ordinateurs via SSH et utilise rsync pour sauvegarder les données. Il fonctionne parfaitement avec des\nordinateurs Linux et un peu moins bien sur des ordinateurs Windows où il faut installer un rsyncd\u002FCygwin (les données ne\nsont pas protégées par SSH dans ce cas).",[11,1812,1813,1814,1819],{},"Par ailleurs, je développe ",[48,1815,1818],{"href":1816,"rel":1817},"https:\u002F\u002Fshadoware.org\u002Fpost\u002Fwoodstock",[346],"mon propre logiciel de sauvegarde",". C'est un défi\npersonnel que je me suis lancé pour répondre à mes propres besoins (et aussi pour le plaisir).",[11,1821,1822,1823,178],{},"Mon premier prototype est écrit en TypeScript et utilise rsync couplé avec Btrfs pour faire des sauvegardes\nincrémentales. Malheureusement, quelques problèmes liés à l'utilisation de Btrfs m'ont fait abandonner ce prototype\n(problème avec la création d'un grand nombre de snapshots et un système de fichier un peu trop plein). J'en parle dans\n",[48,1824,1827],{"href":1825,"rel":1826},"https:\u002F\u002Fshadoware.org\u002Fpost\u002Fwoodstock_brtfs",[346],"un article sur mon blog",[11,1829,1830,1831,1836],{},"Je me suis donc orienté vers l'écriture de mon propre pool de stockage de données. L'écriture dans ce pool de stockage\nm'oblige à écrire mon propre logiciel de synchronisation. BackupPC a fait le choix de rester sur rsync et donc à créer\nun ",[48,1832,1835],{"href":1833,"rel":1834},"https:\u002F\u002Fgithub.com\u002Fbackuppc\u002Frsync-bpc",[346],"fork de rsync"," capable de se connecter en rsync sur les machines à\nsauvegarder, tout en étant capable d'écrire le résultat dans le pool de backuppc, au format de backuppc. Pour ma part,\nj'ai choisi de développer mon protocole basé sur GRPC (et donc HTTP\u002F2).",[11,1838,1839,1840,178],{},"J'ai donc fait un second prototype, toujours en TypeScript, pour tester mon idée. Je suis satisfait du résultat, mais\nles limites du moteur JavaScript font de ce prototype un simple prototype. Là aussi, j'en parle dans\n",[48,1841,1844],{"href":1842,"rel":1843},"https:\u002F\u002Fshadoware.org\u002Fpost\u002Fwoodstock_rust",[346],"un autre article de mon blog",[11,1846,1847],{},"Je vais donc réécrire la partie la plus importante de ce programme en Rust. Pourquoi Rust ? Dans ma jeunesse (enfin,\nj'avais entre 20 et 30 ans), j'adorais programmer en C\u002FC++. Je me souviens d'avoir programmé un petit IDE en C++ avec\nQt. Lors du développement en C++, il m'arrivait parfois de me retrouver avec des fuites de mémoire, des erreurs de\nsegmentation, ainsi que des problèmes de concurrence d'accès aux données.",[11,1849,1850],{},"C++ m'a beaucoup appris sur le fonctionnement d'une machine, la gestion de la mémoire, la gestion du multithreading,\netc. Tout le monde devrait commencer par ce langage 😄.",[11,1852,1853],{},"Rust est un langage qui a été conçu pour éviter ces problèmes. Après avoir lu la documentation, j'ai adoré le concept.\nDu coup, j'ai décidé que ce serait une très bonne idée d'apprendre à l'utiliser. (Surtout qu'on en entend beaucoup\nparler en ce moment).",[11,1855,1856],{},"Vous trouvez que je digresse beaucoup ? C'est possible.",[11,1858,1859],{},"Revenons à notre programme.",[11,1861,1862],{},"Je me suis dit qu'avant d'écrire la gestion de mon pool de stockage en Rust, je voulais faire un script qui me permette\nde migrer le contenu de mon pool de stockage de BackupPC vers mon nouveau pool de stockage. Et de le faire en Rust.",[11,1864,1865],{},"Pour cela, j'ai besoin de comprendre comment fonctionne le pool de stockage de BackupPC. Le faire avec le code source\nde BackupPC sera amusant et pas trop compliqué.",[127,1867,1869],{"id":1868},"description-du-pool-de-stockage-de-backuppc","Description du pool de stockage de BackupPC",[11,1871,1872],{},[120,1873],{"src":1874,"alt":1875},"https:\u002F\u002Fbackuppc.github.io\u002Fbackuppc\u002Fimages\u002Flogos\u002Flogo.svg","BackupPC Logo",[11,1877,1588,1878,1883],{},[48,1879,1882],{"href":1880,"rel":1881},"https:\u002F\u002Fbackuppc.github.io\u002Fbackuppc\u002FBackupPC.html",[346],"documentation de BackupPC"," décrit déjà pas mal de choses :",[505,1885,1886,1893],{},[369,1887,1888],{},[48,1889,1892],{"href":1890,"rel":1891},"https:\u002F\u002Fbackuppc.github.io\u002Fbackuppc\u002FBackupPC.html#Storage-layout",[346],"Comment sont stockés les fichiers dans le pool de stockage ?",[369,1894,1895],{},[48,1896,1899],{"href":1897,"rel":1898},"https:\u002F\u002Fbackuppc.github.io\u002Fbackuppc\u002FBackupPC.html#Compressed-file-format",[346],"Une description succincte du format des fichiers compressés",[11,1901,1902,1903,1908],{},"Ensuite, pour obtenir les détails, il faut aller lire le ",[48,1904,1907],{"href":1905,"rel":1906},"https:\u002F\u002Fgithub.com\u002Fbackuppc\u002Fbackuppc-xs",[346],"code source en C"," qui\nsert de liaison avec le code en Perl. BackupPC est originellement écrit en Perl (entièrement, si je ne me trompe pas,\npour la version 3). La version 4 a été partiellement réécrite en C pour la partie qui gère le pool de stockage. Cette\npartie est utilisée par le rsync modifié et par la partie Perl à travers la bibliothèque de bindings.",[132,1910,1912],{"id":1911},"la-constitution-du-pool","La constitution du pool",[11,1914,1915],{},"Le pool de stockage de BackupPC est principalement constitué de plusieurs dossiers :",[505,1917,1918,1967,1973],{},[369,1919,1920,1923,1924],{},[183,1921,1922],{},"pc"," : Ce dossier répertorie les sauvegardes des différentes machines, organisées par machine puis par sauvegarde.\n",[505,1925,1926,1931],{},[369,1927,1928],{},[183,1929,1930],{},"host1",[369,1932,1933,1936,1937,1939,1940],{},[183,1934,1935],{},"host2"," : Le dossier de la machine ",[183,1938,1935],{},".\n",[505,1941,1942,1948,1954,1959,1964],{},[369,1943,1944,1947],{},[183,1945,1946],{},"backups"," : Un fichier au format TSV (Tab Separated Values) qui liste les sauvegardes de la machine avec leurs\ninformations.",[369,1949,1950,1953],{},[183,1951,1952],{},"1"," : La sauvegarde où les dossiers sont représentés à l'aide du système de fichiers, et les fichiers sont listés\ndans des fichiers d'attributs. Le hash du fichier d'attribut dans le pool est contenu dans le nom du fichier\n(exemple : attrib_33fe8f9ae2f5cedbea63b9d3ea767ac0).",[369,1955,1956],{},[183,1957,1958],{},"2",[369,1960,1961],{},[183,1962,1963],{},"3",[369,1965,1966],{},"...",[369,1968,1969,1972],{},[183,1970,1971],{},"pool"," : Dans ce dossier, les fichiers sont stockés en utilisant leur hash MD5. Les fichiers sont répartis sur deux\nniveaux dans 128 dossiers dont le nom est constitué des 7 premiers bits des deux premiers octets. Si deux fichiers ont\nle même MD5, un nombre est accolé au hash pour les différencier.",[369,1974,1975,1978,1979,1981],{},[183,1976,1977],{},"cpool"," : La structure de ce dossier est la même que celle du dossier ",[183,1980,1971],{},". Il s'agit d'un pool de stockage\ncompressé.",[132,1983,1985],{"id":1984},"le-format-des-fichiers-compressés","Le format des fichiers compressés",[11,1987,1988],{},"Voici la description du fichier compressé selon la documentation (traduction libre) :",[1990,1991,1992,1995],"blockquote",{},[11,1993,1994],{},"Le format de fichier compressé est généré par Compress::Zlib::deflate avec une modification mineure, mais\nimportante. Comme Compress::Zlib::inflate gonfle entièrement son argument en mémoire, il pourrait consommer de\ngrandes quantités de mémoire s'il décompressait un fichier très compressé. Par exemple, un fichier de 200 Mo de 0x0\nbytes se compresse à environ 200 Ko. Si Compress::Zlib::inflate était appelé avec ce seul tampon de 200 Ko, il\naurait besoin d'allouer 200 Mo de mémoire pour retourner le résultat.",[11,1996,1997],{},"BackupPC surveille l'efficacité de la compression d'un fichier. Si un gros fichier a un taux de compression très élevé\n(ce qui signifie qu'il utilisera beaucoup de mémoire lorsqu'il sera décompressé), BackupPC appelle la méthode flush(), qui\ntermine proprement la compression en cours. BackupPC commence alors une nouvelle section et ajoute simplement le\nfichier de sortie. Ainsi, le format de fichier compressé de BackupPC est une ou plusieurs sections\u002Fflushes\nconcaténées. Les ratios spécifiques que BackupPC utilise sont que si un morceau de 6 Mo se compresse à moins de 64\nKo, alors un flush sera effectué.",[11,1999,2000],{},"Donc, dans notre cas, nous devons être capables de lire un fichier compressé comme une suite de fichiers compressés\nconcaténés les uns après les autres.",[132,2002,2004],{"id":2003},"le-format-des-fichiers-dattributs","Le format des fichiers d'attributs",[11,2006,2007,2012,2013,2016,2017,2020],{},[48,2008,2011],{"href":2009,"rel":2010},"https:\u002F\u002Fbackuppc.github.io\u002Fbackuppc\u002FBackupPC.html#Attribute-file-format",[346],"Dans la documentation",", il est expliqué que\ndans la version 4, on retrouve un fichier ",[183,2014,2015],{},"attrib_33fe8f9ae2f5cedbea63b9d3ea767ac0"," dans les différents dossiers de la\nmachine (",[183,2018,2019],{},"__TOPDIR__\u002Fpc\u002Fhost1",").\nLe nom du fichier contient le hash du fichier d'attributs que l'on peut retrouver dans le pool de stockage.",[11,2022,2023,2024,2027],{},"Pour accéder à un fichier du pool de stockage, il faut aller dans le dossier ",[183,2025,2026],{},"__TOPDIR__\u002Fpc"," et lire le nom du fichier\nd'attribut pour retrouver le hash du fichier compressé correspondant au nom du dossier que l'on veut lire. Enfin, il\nfaut lire le fichier compressé pour obtenir les données.",[11,2029,2030,2031,178],{},"Le contenu du fichier d'attribut n'est pas décrit. Cependant, on peut le retrouver dans le fichier\n",[48,2032,2035],{"href":2033,"rel":2034},"https:\u002F\u002Fgithub.com\u002Fbackuppc\u002Fbackuppc-xs\u002Fblob\u002Fmaster\u002Fbpc_attrib.c#L626",[346],"bpc_attribs.c",[11,2037,2038],{},"Le fichier d'attribut est encodé en binaire. Il s'agit d'une suite de varint (entier encodé sur un nombre variable\nd'octets).",[11,2040,2041,2042,2047],{},"Un ",[48,2043,2046],{"href":2044,"rel":2045},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FVariable-length_quantity",[346],"Varint"," est un entier qui est encodé sur un nombre variable\nd'octets. Le premier bit de chaque octet indique si l'entier est terminé ou s'il faut lire un autre octet. Le reste des\nbits de l'octet représente l'entier.",[11,2049,2050],{},"Voici une représentation d'un Varint depuis Wikipedia :",[11,2052,2053],{},[120,2054],{"src":2055,"alt":2056,"className":2057},"https:\u002F\u002Fupload.wikimedia.org\u002Fwikipedia\u002Fcommons\u002Fthumb\u002Fc\u002Fc6\u002FUintvar_coding.svg\u002F1920px-Uintvar_coding.svg.png","Image représentant un Varint (Source : Wikipedia)",[125],[11,2059,2060],{},"D'après le code source de BackupPC, le fichier d'attribut est encodé de la manière suivante :",[11,2062,2063,2064,2067],{},"Le fichier commence par un numéro magique ",[183,2065,2066],{},"0x17565353",". Ensuite, pour chaque fichier, on retrouve les attributs du\nfichier. Pour chaque attribut, on retrouve les xattrs.",[120,2069],{"src":2070},"https:\u002F\u002Fwww.plantuml.com\u002Fplantuml\u002Fpng\u002FXL8nJWCn4Epl5TC11H8eEf2kIa530HqIhHDlxbRosSUkVKgUb7VaOyW7KKWuGsLxPpIptjcbHYDnPtfNTMGiIOExxVrZ_L0lwaLbOXHUfQXD1TnYDC8-Dd31jucIm2RuqETZm-kEIIe0q2ZissOEEYhuqA-4OA_8HpdiIM49SJSGjjdpZ3kLBStgM1EfvD47ItXxVNul4HBR4jIMeMZOkQ8f--nw0g4cZTGQiOVz-GHu99FolzQX7uHKEVZ_naLmJ5unT3lbZqGAJG8tFvkVANL6kqlnLTfiSjuN6AvVdcgao8xriCSGlXo4eeGqai0QhxaoXE1k9gKfisQw_hac17SC_9jywg98-Ar6S0QZSST7KNEEioHlyxf_0W00",[11,2072,2073],{},"Il ne me reste donc plus qu'à reproduire tout cela en Rust.",[127,2075,2077],{"id":2076},"le-développement-en-rust","Le développement en Rust",[11,2079,2080],{},[120,2081],{"src":2082,"alt":1053,"className":2083},"\u002FWoodstock\u002Frust_unsplash.jpg",[125],[11,2085,2086],{},"Pour le développement de ce programme, j'ai établi certaines limites. Je me suis concentré uniquement sur la lecture des\nfichiers du pool qui sont en version 4. Ma version de BackupPC est la version 4, et j'ai migré l'intégralité du pool de\nstockage depuis la version 3.",[11,2088,2089],{},"Je n'ai donc pas de données en version 3 pour tester mon programme.",[11,2091,2092],{},[154,2093,2094],{},"Si vous souhaitez tester mon programme ou l'utiliser, sachez qu'il n'est pas compatible avec la version 3 de BackupPC,\nni avec la version 4 si votre pool est un mélange de versions V3 et V4.",[11,2096,2097],{},"Si le besoin se fait sentir plus tard, il sera toujours possible d'améliorer tout cela.",[132,2099,2101],{"id":2100},"les-fichiers-compressés","Les fichiers compressés",[11,2103,2104,2105,2108,2109,2112,2113,178],{},"J'ai donc commencé par développer un programme qui, à partir d'un hash, est capable de décompresser un fichier de ce\npool. Pour décompresser un fichier BackupPC compressé avec ",[183,2106,2107],{},"zlib",", j'ai choisi d'utiliser la bibliothèque ",[183,2110,2111],{},"flate2",", qui\nest une alternative Rust à la bibliothèque standard ",[183,2114,2107],{},[11,2116,2117,2118,2120],{},"La bibliothèque ",[183,2119,2111],{}," permet de décompresser un fichier en utilisant la notion de BufReader en Rust. La version\nsimplifiée pour décompresser un fichier du pool est donc la suivante :",[2122,2123,2126],"pre",{"className":2124,"code":2125,"language":1774,"meta":1645,"style":1645},"language-rust shiki shiki-themes one-dark-pro","use flate2::bufread::ZlibDecoder;\nuse std::fs::File;\n\nfn main() {\n    let f = File::open(\"33fe8f9ae2f5cedbea63b9d3ea767ac0\").unwrap();\n    let b = BufReader::new(f);\n    let mut z = ZlibEncoder::new(b);\n    let mut buffer = Vec::new();\n    z.read_to_end(&mut buffer).unwrap();\n    println!(\"{:?}\", buffer);\n}\n",[183,2127,2128,2156,2175,2180,2193,2231,2257,2284,2305,2330,2348],{"__ignoreMap":1645},[2129,2130,2133,2137,2141,2145,2148,2150,2153],"span",{"class":2131,"line":2132},"line",1,[2129,2134,2136],{"class":2135},"seHd6","use",[2129,2138,2140],{"class":2139},"sU0A5"," flate2",[2129,2142,2144],{"class":2143},"sn6KH","::",[2129,2146,2147],{"class":2139},"bufread",[2129,2149,2144],{"class":2143},[2129,2151,2152],{"class":2139},"ZlibDecoder",[2129,2154,2155],{"class":2143},";\n",[2129,2157,2158,2160,2163,2165,2168,2170,2173],{"class":2131,"line":1646},[2129,2159,2136],{"class":2135},[2129,2161,2162],{"class":2139}," std",[2129,2164,2144],{"class":2143},[2129,2166,2167],{"class":2139},"fs",[2129,2169,2144],{"class":2143},[2129,2171,2172],{"class":2139},"File",[2129,2174,2155],{"class":2143},[2129,2176,2177],{"class":2131,"line":1651},[2129,2178,2179],{"emptyLinePlaceholder":1766},"\n",[2129,2181,2183,2186,2190],{"class":2131,"line":2182},4,[2129,2184,2185],{"class":2135},"fn",[2129,2187,2189],{"class":2188},"sVbv2"," main",[2129,2191,2192],{"class":2143},"() {\n",[2129,2194,2196,2199,2203,2207,2210,2212,2215,2218,2222,2225,2228],{"class":2131,"line":2195},5,[2129,2197,2198],{"class":2135},"    let",[2129,2200,2202],{"class":2201},"sVyAn"," f",[2129,2204,2206],{"class":2205},"sjrmR"," =",[2129,2208,2209],{"class":2139}," File",[2129,2211,2144],{"class":2143},[2129,2213,2214],{"class":2188},"open",[2129,2216,2217],{"class":2143},"(",[2129,2219,2221],{"class":2220},"subq3","\"33fe8f9ae2f5cedbea63b9d3ea767ac0\"",[2129,2223,2224],{"class":2143},").",[2129,2226,2227],{"class":2188},"unwrap",[2129,2229,2230],{"class":2143},"();\n",[2129,2232,2234,2236,2239,2241,2244,2246,2249,2251,2254],{"class":2131,"line":2233},6,[2129,2235,2198],{"class":2135},[2129,2237,2238],{"class":2201}," b",[2129,2240,2206],{"class":2205},[2129,2242,2243],{"class":2139}," BufReader",[2129,2245,2144],{"class":2143},[2129,2247,2248],{"class":2188},"new",[2129,2250,2217],{"class":2143},[2129,2252,2253],{"class":2201},"f",[2129,2255,2256],{"class":2143},");\n",[2129,2258,2260,2262,2265,2268,2270,2273,2275,2277,2279,2282],{"class":2131,"line":2259},7,[2129,2261,2198],{"class":2135},[2129,2263,2264],{"class":2135}," mut",[2129,2266,2267],{"class":2201}," z",[2129,2269,2206],{"class":2205},[2129,2271,2272],{"class":2139}," ZlibEncoder",[2129,2274,2144],{"class":2143},[2129,2276,2248],{"class":2188},[2129,2278,2217],{"class":2143},[2129,2280,2281],{"class":2201},"b",[2129,2283,2256],{"class":2143},[2129,2285,2287,2289,2291,2294,2296,2299,2301,2303],{"class":2131,"line":2286},8,[2129,2288,2198],{"class":2135},[2129,2290,2264],{"class":2135},[2129,2292,2293],{"class":2201}," buffer",[2129,2295,2206],{"class":2205},[2129,2297,2298],{"class":2139}," Vec",[2129,2300,2144],{"class":2143},[2129,2302,2248],{"class":2188},[2129,2304,2230],{"class":2143},[2129,2306,2308,2311,2313,2316,2319,2322,2324,2326,2328],{"class":2131,"line":2307},9,[2129,2309,2310],{"class":2201},"    z",[2129,2312,178],{"class":2143},[2129,2314,2315],{"class":2188},"read_to_end",[2129,2317,2318],{"class":2143},"(&",[2129,2320,2321],{"class":2135},"mut",[2129,2323,2293],{"class":2201},[2129,2325,2224],{"class":2143},[2129,2327,2227],{"class":2188},[2129,2329,2230],{"class":2143},[2129,2331,2333,2336,2338,2341,2343,2346],{"class":2131,"line":2332},10,[2129,2334,2335],{"class":2188},"    println!",[2129,2337,2217],{"class":2143},[2129,2339,2340],{"class":2220},"\"{:?}\"",[2129,2342,931],{"class":2143},[2129,2344,2345],{"class":2201},"buffer",[2129,2347,2256],{"class":2143},[2129,2349,2351],{"class":2131,"line":2350},11,[2129,2352,2353],{"class":2143},"}\n",[11,2355,2356],{},"Malheureusement, ce code ne fonctionne pas à tous les coups. Pour les plus petits fichiers de mon pool, cela fonctionne\ntrès bien, mais quand on se retrouve avec les fichiers volumineux que BackupPC a décidé de découper, cela ne fonctionne\nplus.",[11,2358,2359,2360,2365],{},"De plus, dans certains cas, BackupPC ",[48,2361,2364],{"href":2362,"rel":2363},"https:\u002F\u002Fgithub.com\u002Fbackuppc\u002Fbackuppc-xs\u002Fblob\u002Fmaster\u002Fbpc_fileZIO.c#L214",[346],"remplace certains octets","\npour indiquer que l'on est au début d'un nouveau bloc de données zlib, ou pour indiquer que l'on a ajouté à la fin une\nchecksum md4.",[11,2367,2368,2369,2371],{},"Comme certains fichiers peuvent être volumineux, je ne peux pas faire un ",[183,2370,2315],{}," pour lire le fichier en\nmémoire et le modifier.",[11,2373,2374,2375,2380],{},"Le fichier ",[48,2376,2379],{"href":2377,"rel":2378},"https:\u002F\u002Fgithub.com\u002Fbackuppc\u002Fbackuppc-xs\u002Fblob\u002Fmaster\u002Fbpc_fileZIO.c",[346],"bpc_fileZIO.c"," permet de comprendre\ncomment sont encodés les fichiers compressés.",[11,2382,2383],{},"Lors de la lecture d'un fichier compressé, si la lecture de la compression s'arrête et qu'il reste encore des octets\nà lire, alors il faut recommencer une nouvelle décompression.",[11,2385,2386],{},"On peut aussi voir le bout de code suivant :",[2122,2388,2392],{"className":2389,"code":2390,"language":2391,"meta":1645,"style":1645},"language-c shiki shiki-themes one-dark-pro","\u002F\u002F https:\u002F\u002Fgithub.com\u002Fbackuppc\u002Fbackuppc-xs\u002Fblob\u002Fmaster\u002Fbpc_fileZIO.c#L219C15-L237C18\nif ( fd->strm.next_in[0] == 0xd6 || fd->strm.next_in[0] == 0xd7 ) {\n    \u002F*\n     * Flag 0xd6 or 0xd7 means this is a compressed file with\n     * appended md4 block checksums for rsync.  Change\n     * the first byte back to 0x78 and proceed.\n     *\u002F\n    fd->strm.next_in[0] = 0x78;\n} else if ( fd->strm.next_in[0] == 0xb3 ) {\n    \u002F*\n     * Flag 0xb3 means this is the start of the rsync\n     * block checksums, so consider this as EOF for\n     * the compressed file.  Also seek the file so\n     * it is positioned at the 0xb3.\n     *\u002F\n    fd->eof = 1;\n    \u002F* TODO: check return status *\u002F\n    lseek(fd->fd, -fd->strm.avail_in, SEEK_CUR);\n    fd->strm.avail_in = 0;\n}\n","c",[183,2393,2394,2400,2464,2469,2474,2479,2484,2489,2519,2553,2557,2562,2568,2574,2580,2585,2602,2608,2642,2662],{"__ignoreMap":1645},[2129,2395,2396],{"class":2131,"line":2132},[2129,2397,2399],{"class":2398},"sV9Aq","\u002F\u002F https:\u002F\u002Fgithub.com\u002Fbackuppc\u002Fbackuppc-xs\u002Fblob\u002Fmaster\u002Fbpc_fileZIO.c#L219C15-L237C18\n",[2129,2401,2402,2405,2408,2411,2414,2417,2420,2424,2427,2430,2433,2436,2439,2442,2444,2446,2448,2450,2452,2454,2456,2458,2461],{"class":2131,"line":1646},[2129,2403,2404],{"class":2135},"if",[2129,2406,2407],{"class":2143}," ( fd",[2129,2409,2410],{"class":2135},"->",[2129,2412,2413],{"class":2143},"strm.",[2129,2415,2416],{"class":2201},"next_in",[2129,2418,2419],{"class":2143},"[",[2129,2421,2423],{"class":2422},"sVC51","0",[2129,2425,2426],{"class":2143},"] ",[2129,2428,2429],{"class":2135},"==",[2129,2431,2432],{"class":2201}," 0x",[2129,2434,2435],{"class":2422},"d6",[2129,2437,2438],{"class":2205}," ||",[2129,2440,2441],{"class":2143}," fd",[2129,2443,2410],{"class":2135},[2129,2445,2413],{"class":2143},[2129,2447,2416],{"class":2201},[2129,2449,2419],{"class":2143},[2129,2451,2423],{"class":2422},[2129,2453,2426],{"class":2143},[2129,2455,2429],{"class":2135},[2129,2457,2432],{"class":2201},[2129,2459,2460],{"class":2422},"d7",[2129,2462,2463],{"class":2143}," ) {\n",[2129,2465,2466],{"class":2131,"line":1651},[2129,2467,2468],{"class":2398},"    \u002F*\n",[2129,2470,2471],{"class":2131,"line":2182},[2129,2472,2473],{"class":2398},"     * Flag 0xd6 or 0xd7 means this is a compressed file with\n",[2129,2475,2476],{"class":2131,"line":2195},[2129,2477,2478],{"class":2398},"     * appended md4 block checksums for rsync.  Change\n",[2129,2480,2481],{"class":2131,"line":2233},[2129,2482,2483],{"class":2398},"     * the first byte back to 0x78 and proceed.\n",[2129,2485,2486],{"class":2131,"line":2259},[2129,2487,2488],{"class":2398},"     *\u002F\n",[2129,2490,2491,2494,2496,2499,2501,2503,2505,2507,2509,2512,2514,2517],{"class":2131,"line":2286},[2129,2492,2493],{"class":2139},"    fd",[2129,2495,2410],{"class":2143},[2129,2497,2498],{"class":2139},"strm",[2129,2500,178],{"class":2143},[2129,2502,2416],{"class":2201},[2129,2504,2419],{"class":2143},[2129,2506,2423],{"class":2422},[2129,2508,2426],{"class":2143},[2129,2510,2511],{"class":2135},"=",[2129,2513,2432],{"class":2201},[2129,2515,2516],{"class":2422},"78",[2129,2518,2155],{"class":2143},[2129,2520,2521,2524,2527,2530,2532,2534,2536,2538,2540,2542,2544,2546,2548,2551],{"class":2131,"line":2307},[2129,2522,2523],{"class":2143},"} ",[2129,2525,2526],{"class":2135},"else",[2129,2528,2529],{"class":2135}," if",[2129,2531,2407],{"class":2143},[2129,2533,2410],{"class":2135},[2129,2535,2413],{"class":2143},[2129,2537,2416],{"class":2201},[2129,2539,2419],{"class":2143},[2129,2541,2423],{"class":2422},[2129,2543,2426],{"class":2143},[2129,2545,2429],{"class":2135},[2129,2547,2432],{"class":2201},[2129,2549,2550],{"class":2422},"b3",[2129,2552,2463],{"class":2143},[2129,2554,2555],{"class":2131,"line":2332},[2129,2556,2468],{"class":2398},[2129,2558,2559],{"class":2131,"line":2350},[2129,2560,2561],{"class":2398},"     * Flag 0xb3 means this is the start of the rsync\n",[2129,2563,2565],{"class":2131,"line":2564},12,[2129,2566,2567],{"class":2398},"     * block checksums, so consider this as EOF for\n",[2129,2569,2571],{"class":2131,"line":2570},13,[2129,2572,2573],{"class":2398},"     * the compressed file.  Also seek the file so\n",[2129,2575,2577],{"class":2131,"line":2576},14,[2129,2578,2579],{"class":2398},"     * it is positioned at the 0xb3.\n",[2129,2581,2583],{"class":2131,"line":2582},15,[2129,2584,2488],{"class":2398},[2129,2586,2588,2590,2592,2595,2597,2600],{"class":2131,"line":2587},16,[2129,2589,2493],{"class":2139},[2129,2591,2410],{"class":2143},[2129,2593,2594],{"class":2201},"eof",[2129,2596,2206],{"class":2135},[2129,2598,2599],{"class":2422}," 1",[2129,2601,2155],{"class":2143},[2129,2603,2605],{"class":2131,"line":2604},17,[2129,2606,2607],{"class":2398},"    \u002F* TODO: check return status *\u002F\n",[2129,2609,2611,2614,2616,2619,2621,2623,2625,2628,2630,2632,2634,2636,2639],{"class":2131,"line":2610},18,[2129,2612,2613],{"class":2188},"    lseek",[2129,2615,2217],{"class":2143},[2129,2617,2618],{"class":2139},"fd",[2129,2620,2410],{"class":2143},[2129,2622,2618],{"class":2201},[2129,2624,931],{"class":2143},[2129,2626,2627],{"class":2135},"-",[2129,2629,2618],{"class":2139},[2129,2631,2410],{"class":2143},[2129,2633,2498],{"class":2139},[2129,2635,178],{"class":2143},[2129,2637,2638],{"class":2201},"avail_in",[2129,2640,2641],{"class":2143},", SEEK_CUR);\n",[2129,2643,2645,2647,2649,2651,2653,2655,2657,2660],{"class":2131,"line":2644},19,[2129,2646,2493],{"class":2139},[2129,2648,2410],{"class":2143},[2129,2650,2498],{"class":2139},[2129,2652,178],{"class":2143},[2129,2654,2638],{"class":2201},[2129,2656,2206],{"class":2135},[2129,2658,2659],{"class":2422}," 0",[2129,2661,2155],{"class":2143},[2129,2663,2665],{"class":2131,"line":2664},20,[2129,2666,2353],{"class":2143},[11,2668,2669,2670,938,2673,2676,2677,2680,2681,2684],{},"Si, en début de flux, on trouve ",[183,2671,2672],{},"0xd6",[183,2674,2675],{},"0xd7",", il faut changer le premier octet en ",[183,2678,2679],{},"0x78"," et continuer la lecture. En\nfin de flux, si on trouve ",[183,2682,2683],{},"0xb3",", on considère que c'est la fin du fichier compressé. (De notre côté, nous ne nous\nintéresserons pas à la partie checksum md4).",[11,2686,2117,2687,2689,2690,2692],{},[183,2688,2111],{}," ne permet pas de faire cela simplement. J'ai donc dû lire le code de ",[183,2691,2111],{}," pour comprendre\ncomment je pouvais agir pour lire un fichier compressé de BackupPC.",[11,2694,2695,2702,2703,2705,2706,2709,2710,2713,2714,2717],{},[48,2696,2699,2700],{"href":2697,"rel":2698},"https:\u002F\u002Fgithub.com\u002Frust-lang\u002Fflate2-rs\u002Fblob\u002Fmain\u002Fsrc\u002Fbufreader.rs#L73",[346],"Le code suivant de la bibliothèque ",[183,2701,2111],{},"\npermet de voir que ",[183,2704,2111],{}," a besoin, dans son constructeur, d'un ",[183,2707,2708],{},"BufRead"," pour lire un fichier compressé. Il utilise\nla méthode ",[183,2711,2712],{},"fill_buf"," pour remplir un tampon et la méthode ",[183,2715,2716],{},"consume"," pour consommer le tampon.",[2122,2719,2721],{"className":2124,"code":2720,"language":1774,"meta":1645,"style":1645},"\u002F\u002F https:\u002F\u002Fgithub.com\u002Frust-lang\u002Fflate2-rs\u002Fblob\u002Fmain\u002Fsrc\u002Fbufreader.rs#L73\nimpl\u003CR: Read> Read for BufReader\u003CR> {\n    fn read(&mut self, buf: &mut [u8]) -> io::Result\u003Cusize> {\n        \u002F\u002F If we don't have any buffered data and we're doing a massive read\n        \u002F\u002F (larger than our internal buffer), bypass our internal buffer\n        \u002F\u002F entirely.\n        if self.pos == self.cap && buf.len() >= self.buf.len() {\n            return self.inner.read(buf);\n        }\n        let nread = {\n            let mut rem = self.fill_buf()?;\n            rem.read(buf)?\n        };\n        self.consume(nread);\n        Ok(nread)\n    }\n}\n",[183,2722,2723,2728,2762,2811,2816,2821,2826,2869,2888,2893,2906,2927,2943,2948,2964,2976,2981],{"__ignoreMap":1645},[2129,2724,2725],{"class":2131,"line":2132},[2129,2726,2727],{"class":2398},"\u002F\u002F https:\u002F\u002Fgithub.com\u002Frust-lang\u002Fflate2-rs\u002Fblob\u002Fmain\u002Fsrc\u002Fbufreader.rs#L73\n",[2129,2729,2730,2733,2736,2739,2742,2745,2748,2750,2753,2755,2757,2759],{"class":2131,"line":1646},[2129,2731,2732],{"class":2135},"impl",[2129,2734,2735],{"class":2143},"\u003C",[2129,2737,2738],{"class":2139},"R",[2129,2740,2741],{"class":2143},": ",[2129,2743,2744],{"class":2139},"Read",[2129,2746,2747],{"class":2143},"> ",[2129,2749,2744],{"class":2139},[2129,2751,2752],{"class":2135}," for",[2129,2754,2243],{"class":2139},[2129,2756,2735],{"class":2143},[2129,2758,2738],{"class":2139},[2129,2760,2761],{"class":2143},"> {\n",[2129,2763,2764,2767,2770,2772,2774,2777,2779,2782,2785,2787,2790,2793,2796,2799,2801,2804,2806,2809],{"class":2131,"line":1651},[2129,2765,2766],{"class":2135},"    fn",[2129,2768,2769],{"class":2188}," read",[2129,2771,2318],{"class":2143},[2129,2773,2321],{"class":2135},[2129,2775,2776],{"class":2139}," self",[2129,2778,931],{"class":2143},[2129,2780,2781],{"class":2201},"buf",[2129,2783,2784],{"class":2143},": &",[2129,2786,2321],{"class":2135},[2129,2788,2789],{"class":2143}," [",[2129,2791,2792],{"class":2139},"u8",[2129,2794,2795],{"class":2143},"]) -> ",[2129,2797,2798],{"class":2139},"io",[2129,2800,2144],{"class":2143},[2129,2802,2803],{"class":2139},"Result",[2129,2805,2735],{"class":2143},[2129,2807,2808],{"class":2139},"usize",[2129,2810,2761],{"class":2143},[2129,2812,2813],{"class":2131,"line":2182},[2129,2814,2815],{"class":2398},"        \u002F\u002F If we don't have any buffered data and we're doing a massive read\n",[2129,2817,2818],{"class":2131,"line":2195},[2129,2819,2820],{"class":2398},"        \u002F\u002F (larger than our internal buffer), bypass our internal buffer\n",[2129,2822,2823],{"class":2131,"line":2233},[2129,2824,2825],{"class":2398},"        \u002F\u002F entirely.\n",[2129,2827,2828,2831,2833,2836,2838,2840,2843,2846,2849,2851,2854,2857,2860,2862,2865,2867],{"class":2131,"line":2259},[2129,2829,2830],{"class":2135},"        if",[2129,2832,2776],{"class":2139},[2129,2834,2835],{"class":2143},".pos ",[2129,2837,2429],{"class":2205},[2129,2839,2776],{"class":2139},[2129,2841,2842],{"class":2143},".cap ",[2129,2844,2845],{"class":2205},"&&",[2129,2847,2848],{"class":2201}," buf",[2129,2850,178],{"class":2143},[2129,2852,2853],{"class":2188},"len",[2129,2855,2856],{"class":2143},"() ",[2129,2858,2859],{"class":2205},">=",[2129,2861,2776],{"class":2139},[2129,2863,2864],{"class":2143},".buf.",[2129,2866,2853],{"class":2188},[2129,2868,2192],{"class":2143},[2129,2870,2871,2874,2876,2879,2882,2884,2886],{"class":2131,"line":2286},[2129,2872,2873],{"class":2135},"            return",[2129,2875,2776],{"class":2139},[2129,2877,2878],{"class":2143},".inner.",[2129,2880,2881],{"class":2188},"read",[2129,2883,2217],{"class":2143},[2129,2885,2781],{"class":2201},[2129,2887,2256],{"class":2143},[2129,2889,2890],{"class":2131,"line":2307},[2129,2891,2892],{"class":2143},"        }\n",[2129,2894,2895,2898,2901,2903],{"class":2131,"line":2332},[2129,2896,2897],{"class":2135},"        let",[2129,2899,2900],{"class":2201}," nread",[2129,2902,2206],{"class":2205},[2129,2904,2905],{"class":2143}," {\n",[2129,2907,2908,2911,2913,2916,2918,2920,2922,2924],{"class":2131,"line":2350},[2129,2909,2910],{"class":2135},"            let",[2129,2912,2264],{"class":2135},[2129,2914,2915],{"class":2201}," rem",[2129,2917,2206],{"class":2205},[2129,2919,2776],{"class":2139},[2129,2921,178],{"class":2143},[2129,2923,2712],{"class":2188},[2129,2925,2926],{"class":2143},"()?;\n",[2129,2928,2929,2932,2934,2936,2938,2940],{"class":2131,"line":2564},[2129,2930,2931],{"class":2201},"            rem",[2129,2933,178],{"class":2143},[2129,2935,2881],{"class":2188},[2129,2937,2217],{"class":2143},[2129,2939,2781],{"class":2201},[2129,2941,2942],{"class":2143},")?\n",[2129,2944,2945],{"class":2131,"line":2570},[2129,2946,2947],{"class":2143},"        };\n",[2129,2949,2950,2953,2955,2957,2959,2962],{"class":2131,"line":2576},[2129,2951,2952],{"class":2139},"        self",[2129,2954,178],{"class":2143},[2129,2956,2716],{"class":2188},[2129,2958,2217],{"class":2143},[2129,2960,2961],{"class":2201},"nread",[2129,2963,2256],{"class":2143},[2129,2965,2966,2969,2971,2973],{"class":2131,"line":2582},[2129,2967,2968],{"class":2139},"        Ok",[2129,2970,2217],{"class":2143},[2129,2972,2961],{"class":2201},[2129,2974,2975],{"class":2143},")\n",[2129,2977,2978],{"class":2131,"line":2587},[2129,2979,2980],{"class":2143},"    }\n",[2129,2982,2983],{"class":2131,"line":2604},[2129,2984,2353],{"class":2143},[11,2986,2987],{},"J'ai donc commencé par développer un adaptateur qui s'intercale entre le fichier et le décompresseur. Cet adaptateur\na pour objectif de reproduire le comportement de BackupPC et de remplacer les octets en début de fichier.",[2122,2989,2991],{"className":2124,"code":2990,"language":1774,"meta":1645,"style":1645},"struct InterpretAdapter\u003CR: BufRead> {\n    inner: R,\n    first: bool,\n    temp: Option\u003CVec\u003Cu8>>,\n}\n\nimpl\u003CR: BufRead> InterpretAdapter\u003CR> {\n    fn new(inner: R) -> Self {\n        Self {\n            inner,\n            first: true,\n            temp: None,\n        }\n    }\n\n    fn reset(&mut self) {\n        self.first = true;\n        self.temp = None;\n    }\n}\n...\nimpl\u003CR: BufRead> BufRead for InterpretAdapter\u003CR> {\n    fn fill_buf(&mut self) -> io::Result\u003C&[u8]> {\n        if self.temp.is_none() {\n            let buf = self.inner.fill_buf()?;\n            let mut buf = buf.to_vec();\n\n            if self.first && !buf.is_empty() {\n                self.first = false;\n\n                if buf[0] == 0xd6 || buf[0] == 0xd7 {\n                    buf[0] = 0x78;\n                } else if buf[0] == 0xb3 {\n                    \u002F\u002F EOF\n                    buf = Vec::new();\n                }\n            }\n\n            self.temp = Some(buf);\n        }\n\n        Ok(self.temp.as_ref().unwrap())\n    }\n\n    fn consume(&mut self, amt: usize) {\n        if amt > 0 {\n            self.temp = None;\n            self.inner.consume(amt);\n        }\n    }\n}\n",[183,2992,2993,3011,3023,3035,3057,3061,3065,3088,3112,3119,3126,3138,3150,3154,3158,3162,3178,3192,3206,3210,3214,3220,3246,3276,3291,3308,3328,3333,3357,3372,3377,3413,3432,3457,3463,3478,3484,3490,3495,3514,3519,3524,3547,3552,3557,3582,3597,3610,3625,3630,3635],{"__ignoreMap":1645},[2129,2994,2995,2998,3001,3003,3005,3007,3009],{"class":2131,"line":2132},[2129,2996,2997],{"class":2135},"struct",[2129,2999,3000],{"class":2139}," InterpretAdapter",[2129,3002,2735],{"class":2143},[2129,3004,2738],{"class":2139},[2129,3006,2741],{"class":2143},[2129,3008,2708],{"class":2139},[2129,3010,2761],{"class":2143},[2129,3012,3013,3016,3018,3020],{"class":2131,"line":1646},[2129,3014,3015],{"class":2201},"    inner",[2129,3017,2741],{"class":2143},[2129,3019,2738],{"class":2139},[2129,3021,3022],{"class":2143},",\n",[2129,3024,3025,3028,3030,3033],{"class":2131,"line":1651},[2129,3026,3027],{"class":2201},"    first",[2129,3029,2741],{"class":2143},[2129,3031,3032],{"class":2139},"bool",[2129,3034,3022],{"class":2143},[2129,3036,3037,3040,3042,3045,3047,3050,3052,3054],{"class":2131,"line":2182},[2129,3038,3039],{"class":2201},"    temp",[2129,3041,2741],{"class":2143},[2129,3043,3044],{"class":2139},"Option",[2129,3046,2735],{"class":2143},[2129,3048,3049],{"class":2139},"Vec",[2129,3051,2735],{"class":2143},[2129,3053,2792],{"class":2139},[2129,3055,3056],{"class":2143},">>,\n",[2129,3058,3059],{"class":2131,"line":2195},[2129,3060,2353],{"class":2143},[2129,3062,3063],{"class":2131,"line":2233},[2129,3064,2179],{"emptyLinePlaceholder":1766},[2129,3066,3067,3069,3071,3073,3075,3077,3079,3082,3084,3086],{"class":2131,"line":2259},[2129,3068,2732],{"class":2135},[2129,3070,2735],{"class":2143},[2129,3072,2738],{"class":2139},[2129,3074,2741],{"class":2143},[2129,3076,2708],{"class":2139},[2129,3078,2747],{"class":2143},[2129,3080,3081],{"class":2139},"InterpretAdapter",[2129,3083,2735],{"class":2143},[2129,3085,2738],{"class":2139},[2129,3087,2761],{"class":2143},[2129,3089,3090,3092,3095,3097,3100,3102,3104,3107,3110],{"class":2131,"line":2286},[2129,3091,2766],{"class":2135},[2129,3093,3094],{"class":2188}," new",[2129,3096,2217],{"class":2143},[2129,3098,3099],{"class":2201},"inner",[2129,3101,2741],{"class":2143},[2129,3103,2738],{"class":2139},[2129,3105,3106],{"class":2143},") -> ",[2129,3108,3109],{"class":2139},"Self",[2129,3111,2905],{"class":2143},[2129,3113,3114,3117],{"class":2131,"line":2307},[2129,3115,3116],{"class":2139},"        Self",[2129,3118,2905],{"class":2143},[2129,3120,3121,3124],{"class":2131,"line":2332},[2129,3122,3123],{"class":2201},"            inner",[2129,3125,3022],{"class":2143},[2129,3127,3128,3131,3133,3136],{"class":2131,"line":2350},[2129,3129,3130],{"class":2201},"            first",[2129,3132,2741],{"class":2143},[2129,3134,3135],{"class":2422},"true",[2129,3137,3022],{"class":2143},[2129,3139,3140,3143,3145,3148],{"class":2131,"line":2564},[2129,3141,3142],{"class":2201},"            temp",[2129,3144,2741],{"class":2143},[2129,3146,3147],{"class":2139},"None",[2129,3149,3022],{"class":2143},[2129,3151,3152],{"class":2131,"line":2570},[2129,3153,2892],{"class":2143},[2129,3155,3156],{"class":2131,"line":2576},[2129,3157,2980],{"class":2143},[2129,3159,3160],{"class":2131,"line":2582},[2129,3161,2179],{"emptyLinePlaceholder":1766},[2129,3163,3164,3166,3169,3171,3173,3175],{"class":2131,"line":2587},[2129,3165,2766],{"class":2135},[2129,3167,3168],{"class":2188}," reset",[2129,3170,2318],{"class":2143},[2129,3172,2321],{"class":2135},[2129,3174,2776],{"class":2139},[2129,3176,3177],{"class":2143},") {\n",[2129,3179,3180,3182,3185,3187,3190],{"class":2131,"line":2604},[2129,3181,2952],{"class":2139},[2129,3183,3184],{"class":2143},".first ",[2129,3186,2511],{"class":2205},[2129,3188,3189],{"class":2422}," true",[2129,3191,2155],{"class":2143},[2129,3193,3194,3196,3199,3201,3204],{"class":2131,"line":2610},[2129,3195,2952],{"class":2139},[2129,3197,3198],{"class":2143},".temp ",[2129,3200,2511],{"class":2205},[2129,3202,3203],{"class":2139}," None",[2129,3205,2155],{"class":2143},[2129,3207,3208],{"class":2131,"line":2644},[2129,3209,2980],{"class":2143},[2129,3211,3212],{"class":2131,"line":2664},[2129,3213,2353],{"class":2143},[2129,3215,3217],{"class":2131,"line":3216},21,[2129,3218,3219],{"class":2143},"...\n",[2129,3221,3222,3224,3226,3228,3230,3232,3234,3236,3238,3240,3242,3244],{"class":2131,"line":1776},[2129,3223,2732],{"class":2135},[2129,3225,2735],{"class":2143},[2129,3227,2738],{"class":2139},[2129,3229,2741],{"class":2143},[2129,3231,2708],{"class":2139},[2129,3233,2747],{"class":2143},[2129,3235,2708],{"class":2139},[2129,3237,2752],{"class":2135},[2129,3239,3000],{"class":2139},[2129,3241,2735],{"class":2143},[2129,3243,2738],{"class":2139},[2129,3245,2761],{"class":2143},[2129,3247,3249,3251,3254,3256,3258,3260,3262,3264,3266,3268,3271,3273],{"class":2131,"line":3248},23,[2129,3250,2766],{"class":2135},[2129,3252,3253],{"class":2188}," fill_buf",[2129,3255,2318],{"class":2143},[2129,3257,2321],{"class":2135},[2129,3259,2776],{"class":2139},[2129,3261,3106],{"class":2143},[2129,3263,2798],{"class":2139},[2129,3265,2144],{"class":2143},[2129,3267,2803],{"class":2139},[2129,3269,3270],{"class":2143},"\u003C&[",[2129,3272,2792],{"class":2139},[2129,3274,3275],{"class":2143},"]> {\n",[2129,3277,3279,3281,3283,3286,3289],{"class":2131,"line":3278},24,[2129,3280,2830],{"class":2135},[2129,3282,2776],{"class":2139},[2129,3284,3285],{"class":2143},".temp.",[2129,3287,3288],{"class":2188},"is_none",[2129,3290,2192],{"class":2143},[2129,3292,3294,3296,3298,3300,3302,3304,3306],{"class":2131,"line":3293},25,[2129,3295,2910],{"class":2135},[2129,3297,2848],{"class":2201},[2129,3299,2206],{"class":2205},[2129,3301,2776],{"class":2139},[2129,3303,2878],{"class":2143},[2129,3305,2712],{"class":2188},[2129,3307,2926],{"class":2143},[2129,3309,3311,3313,3315,3317,3319,3321,3323,3326],{"class":2131,"line":3310},26,[2129,3312,2910],{"class":2135},[2129,3314,2264],{"class":2135},[2129,3316,2848],{"class":2201},[2129,3318,2206],{"class":2205},[2129,3320,2848],{"class":2201},[2129,3322,178],{"class":2143},[2129,3324,3325],{"class":2188},"to_vec",[2129,3327,2230],{"class":2143},[2129,3329,3331],{"class":2131,"line":3330},27,[2129,3332,2179],{"emptyLinePlaceholder":1766},[2129,3334,3336,3339,3341,3343,3345,3348,3350,3352,3355],{"class":2131,"line":3335},28,[2129,3337,3338],{"class":2135},"            if",[2129,3340,2776],{"class":2139},[2129,3342,3184],{"class":2143},[2129,3344,2845],{"class":2205},[2129,3346,3347],{"class":2205}," !",[2129,3349,2781],{"class":2201},[2129,3351,178],{"class":2143},[2129,3353,3354],{"class":2188},"is_empty",[2129,3356,2192],{"class":2143},[2129,3358,3360,3363,3365,3367,3370],{"class":2131,"line":3359},29,[2129,3361,3362],{"class":2139},"                self",[2129,3364,3184],{"class":2143},[2129,3366,2511],{"class":2205},[2129,3368,3369],{"class":2422}," false",[2129,3371,2155],{"class":2143},[2129,3373,3375],{"class":2131,"line":3374},30,[2129,3376,2179],{"emptyLinePlaceholder":1766},[2129,3378,3380,3383,3385,3387,3389,3391,3393,3396,3398,3400,3402,3404,3406,3408,3411],{"class":2131,"line":3379},31,[2129,3381,3382],{"class":2135},"                if",[2129,3384,2848],{"class":2201},[2129,3386,2419],{"class":2143},[2129,3388,2423],{"class":2422},[2129,3390,2426],{"class":2143},[2129,3392,2429],{"class":2205},[2129,3394,3395],{"class":2422}," 0xd6",[2129,3397,2438],{"class":2205},[2129,3399,2848],{"class":2201},[2129,3401,2419],{"class":2143},[2129,3403,2423],{"class":2422},[2129,3405,2426],{"class":2143},[2129,3407,2429],{"class":2205},[2129,3409,3410],{"class":2422}," 0xd7",[2129,3412,2905],{"class":2143},[2129,3414,3416,3419,3421,3423,3425,3427,3430],{"class":2131,"line":3415},32,[2129,3417,3418],{"class":2201},"                    buf",[2129,3420,2419],{"class":2143},[2129,3422,2423],{"class":2422},[2129,3424,2426],{"class":2143},[2129,3426,2511],{"class":2205},[2129,3428,3429],{"class":2422}," 0x78",[2129,3431,2155],{"class":2143},[2129,3433,3435,3438,3440,3442,3444,3446,3448,3450,3452,3455],{"class":2131,"line":3434},33,[2129,3436,3437],{"class":2143},"                } ",[2129,3439,2526],{"class":2135},[2129,3441,2529],{"class":2135},[2129,3443,2848],{"class":2201},[2129,3445,2419],{"class":2143},[2129,3447,2423],{"class":2422},[2129,3449,2426],{"class":2143},[2129,3451,2429],{"class":2205},[2129,3453,3454],{"class":2422}," 0xb3",[2129,3456,2905],{"class":2143},[2129,3458,3460],{"class":2131,"line":3459},34,[2129,3461,3462],{"class":2398},"                    \u002F\u002F EOF\n",[2129,3464,3466,3468,3470,3472,3474,3476],{"class":2131,"line":3465},35,[2129,3467,3418],{"class":2201},[2129,3469,2206],{"class":2205},[2129,3471,2298],{"class":2139},[2129,3473,2144],{"class":2143},[2129,3475,2248],{"class":2188},[2129,3477,2230],{"class":2143},[2129,3479,3481],{"class":2131,"line":3480},36,[2129,3482,3483],{"class":2143},"                }\n",[2129,3485,3487],{"class":2131,"line":3486},37,[2129,3488,3489],{"class":2143},"            }\n",[2129,3491,3493],{"class":2131,"line":3492},38,[2129,3494,2179],{"emptyLinePlaceholder":1766},[2129,3496,3498,3501,3503,3505,3508,3510,3512],{"class":2131,"line":3497},39,[2129,3499,3500],{"class":2139},"            self",[2129,3502,3198],{"class":2143},[2129,3504,2511],{"class":2205},[2129,3506,3507],{"class":2139}," Some",[2129,3509,2217],{"class":2143},[2129,3511,2781],{"class":2201},[2129,3513,2256],{"class":2143},[2129,3515,3517],{"class":2131,"line":3516},40,[2129,3518,2892],{"class":2143},[2129,3520,3522],{"class":2131,"line":3521},41,[2129,3523,2179],{"emptyLinePlaceholder":1766},[2129,3525,3527,3529,3531,3534,3536,3539,3542,3544],{"class":2131,"line":3526},42,[2129,3528,2968],{"class":2139},[2129,3530,2217],{"class":2143},[2129,3532,3533],{"class":2139},"self",[2129,3535,3285],{"class":2143},[2129,3537,3538],{"class":2188},"as_ref",[2129,3540,3541],{"class":2143},"().",[2129,3543,2227],{"class":2188},[2129,3545,3546],{"class":2143},"())\n",[2129,3548,3550],{"class":2131,"line":3549},43,[2129,3551,2980],{"class":2143},[2129,3553,3555],{"class":2131,"line":3554},44,[2129,3556,2179],{"emptyLinePlaceholder":1766},[2129,3558,3560,3562,3565,3567,3569,3571,3573,3576,3578,3580],{"class":2131,"line":3559},45,[2129,3561,2766],{"class":2135},[2129,3563,3564],{"class":2188}," consume",[2129,3566,2318],{"class":2143},[2129,3568,2321],{"class":2135},[2129,3570,2776],{"class":2139},[2129,3572,931],{"class":2143},[2129,3574,3575],{"class":2201},"amt",[2129,3577,2741],{"class":2143},[2129,3579,2808],{"class":2139},[2129,3581,3177],{"class":2143},[2129,3583,3585,3587,3590,3593,3595],{"class":2131,"line":3584},46,[2129,3586,2830],{"class":2135},[2129,3588,3589],{"class":2201}," amt",[2129,3591,3592],{"class":2205}," >",[2129,3594,2659],{"class":2422},[2129,3596,2905],{"class":2143},[2129,3598,3600,3602,3604,3606,3608],{"class":2131,"line":3599},47,[2129,3601,3500],{"class":2139},[2129,3603,3198],{"class":2143},[2129,3605,2511],{"class":2205},[2129,3607,3203],{"class":2139},[2129,3609,2155],{"class":2143},[2129,3611,3613,3615,3617,3619,3621,3623],{"class":2131,"line":3612},48,[2129,3614,3500],{"class":2139},[2129,3616,2878],{"class":2143},[2129,3618,2716],{"class":2188},[2129,3620,2217],{"class":2143},[2129,3622,3575],{"class":2201},[2129,3624,2256],{"class":2143},[2129,3626,3628],{"class":2131,"line":3627},49,[2129,3629,2892],{"class":2143},[2129,3631,3633],{"class":2131,"line":3632},50,[2129,3634,2980],{"class":2143},[2129,3636,3638],{"class":2131,"line":3637},51,[2129,3639,2353],{"class":2143},[11,3641,3642,3643,3646,3647,3650,3651,3653,3654,3656,3657,791,3659,3661],{},"Cet adaptateur s'intercale entre le ",[183,3644,3645],{},"BufReader"," et le ",[183,3648,3649],{},"ZLibDecoder",". Du point de vue du ",[183,3652,3649],{},", il doit\nse comporter comme un ",[183,3655,3645],{}," et donc implémenter les méthodes ",[183,3658,2712],{},[183,3660,2716],{},". Dans ces méthodes, je dois\nretourner un tampon.",[11,3663,3664,3665,3667],{},"Je ne peux pas modifier directement le tampon du ",[183,3666,3645],{}," car ce dernier est une référence en lecture seule. Je\ncopie donc le contenu (je n'ai pas trouvé de meilleure manière de faire cela).",[11,3669,3670,3671,3674,3675,3678,3679,3681],{},"Enfin, on peut développer le ",[183,3672,3673],{},"Reader"," qui sera capable de lire les fichiers compressés. Pour cela, j'ai d'abord\nimplémenté une méthode ",[183,3676,3677],{},"read_some_bytes"," qui permet de lire un certain nombre d'octets. Si jamais on arrive à la fin\nd'une section, alors la méthode peut retourner moins que la taille du tampon. Généralement, cela arrive en fin de\nfichier. Quand j'ai utilisé directement cette méthode en tant que méthode ",[183,3680,2881],{},", je me suis retrouvé avec des\ncorruptions.",[2122,3683,3685],{"className":2124,"code":3684,"language":1774,"meta":1645,"style":1645},"pub struct BackupPCReader\u003CR: Read> {\n    decoder: Option\u003CZlibDecoder\u003CInterpretAdapter\u003CBufReader\u003CR>>>>,\n}\n...\nfn read_some_bytes(&mut self, buf: &mut [u8]) -> io::Result\u003Cusize> {\n    loop {\n        let decoder = self.decoder.as_mut();\n        if decoder.is_none() {\n            return Ok(0);\n        }\n\n        let decoder_read_result = decoder.unwrap().read(buf);\n\n        let count = match decoder_read_result {\n            Ok(count) => {\n                count\n            }\n            Err(e) => {\n                return Err(e);\n            }\n        };\n\n        if count != 0 {\n            return Ok(count);\n        }\n\n        if count == 0 {\n            let decoder = self.decoder.take();\n            if let Some(decoder) = decoder {\n                let mut reader = decoder.into_inner();\n                \u002F\u002F S'il reste encore des octets à lire dans reader alors on continue, sinon on s'arrête\n                if reader.fill_buf()?.is_empty() {\n                    return Ok(0);\n                }\n                reader.reset();\n\n                self.decoder = Some(ZlibDecoder::new(reader));\n            }\n        }\n    }\n}\n",[183,3686,3687,3708,3736,3740,3744,3783,3790,3809,3821,3834,3838,3842,3867,3871,3887,3900,3905,3909,3921,3935,3939,3943,3947,3960,3972,3976,3980,3993,4010,4033,4054,4059,4076,4089,4093,4105,4109,4136,4140,4144,4148],{"__ignoreMap":1645},[2129,3688,3689,3692,3695,3698,3700,3702,3704,3706],{"class":2131,"line":2132},[2129,3690,3691],{"class":2135},"pub",[2129,3693,3694],{"class":2135}," struct",[2129,3696,3697],{"class":2139}," BackupPCReader",[2129,3699,2735],{"class":2143},[2129,3701,2738],{"class":2139},[2129,3703,2741],{"class":2143},[2129,3705,2744],{"class":2139},[2129,3707,2761],{"class":2143},[2129,3709,3710,3713,3715,3717,3719,3721,3723,3725,3727,3729,3731,3733],{"class":2131,"line":1646},[2129,3711,3712],{"class":2201},"    decoder",[2129,3714,2741],{"class":2143},[2129,3716,3044],{"class":2139},[2129,3718,2735],{"class":2143},[2129,3720,2152],{"class":2139},[2129,3722,2735],{"class":2143},[2129,3724,3081],{"class":2139},[2129,3726,2735],{"class":2143},[2129,3728,3645],{"class":2139},[2129,3730,2735],{"class":2143},[2129,3732,2738],{"class":2139},[2129,3734,3735],{"class":2143},">>>>,\n",[2129,3737,3738],{"class":2131,"line":1651},[2129,3739,2353],{"class":2143},[2129,3741,3742],{"class":2131,"line":2182},[2129,3743,3219],{"class":2143},[2129,3745,3746,3748,3751,3753,3755,3757,3759,3761,3763,3765,3767,3769,3771,3773,3775,3777,3779,3781],{"class":2131,"line":2195},[2129,3747,2185],{"class":2135},[2129,3749,3750],{"class":2188}," read_some_bytes",[2129,3752,2318],{"class":2143},[2129,3754,2321],{"class":2135},[2129,3756,2776],{"class":2139},[2129,3758,931],{"class":2143},[2129,3760,2781],{"class":2201},[2129,3762,2784],{"class":2143},[2129,3764,2321],{"class":2135},[2129,3766,2789],{"class":2143},[2129,3768,2792],{"class":2139},[2129,3770,2795],{"class":2143},[2129,3772,2798],{"class":2139},[2129,3774,2144],{"class":2143},[2129,3776,2803],{"class":2139},[2129,3778,2735],{"class":2143},[2129,3780,2808],{"class":2139},[2129,3782,2761],{"class":2143},[2129,3784,3785,3788],{"class":2131,"line":2233},[2129,3786,3787],{"class":2135},"    loop",[2129,3789,2905],{"class":2143},[2129,3791,3792,3794,3797,3799,3801,3804,3807],{"class":2131,"line":2259},[2129,3793,2897],{"class":2135},[2129,3795,3796],{"class":2201}," decoder",[2129,3798,2206],{"class":2205},[2129,3800,2776],{"class":2139},[2129,3802,3803],{"class":2143},".decoder.",[2129,3805,3806],{"class":2188},"as_mut",[2129,3808,2230],{"class":2143},[2129,3810,3811,3813,3815,3817,3819],{"class":2131,"line":2286},[2129,3812,2830],{"class":2135},[2129,3814,3796],{"class":2201},[2129,3816,178],{"class":2143},[2129,3818,3288],{"class":2188},[2129,3820,2192],{"class":2143},[2129,3822,3823,3825,3828,3830,3832],{"class":2131,"line":2307},[2129,3824,2873],{"class":2135},[2129,3826,3827],{"class":2139}," Ok",[2129,3829,2217],{"class":2143},[2129,3831,2423],{"class":2422},[2129,3833,2256],{"class":2143},[2129,3835,3836],{"class":2131,"line":2332},[2129,3837,2892],{"class":2143},[2129,3839,3840],{"class":2131,"line":2350},[2129,3841,2179],{"emptyLinePlaceholder":1766},[2129,3843,3844,3846,3849,3851,3853,3855,3857,3859,3861,3863,3865],{"class":2131,"line":2564},[2129,3845,2897],{"class":2135},[2129,3847,3848],{"class":2201}," decoder_read_result",[2129,3850,2206],{"class":2205},[2129,3852,3796],{"class":2201},[2129,3854,178],{"class":2143},[2129,3856,2227],{"class":2188},[2129,3858,3541],{"class":2143},[2129,3860,2881],{"class":2188},[2129,3862,2217],{"class":2143},[2129,3864,2781],{"class":2201},[2129,3866,2256],{"class":2143},[2129,3868,3869],{"class":2131,"line":2570},[2129,3870,2179],{"emptyLinePlaceholder":1766},[2129,3872,3873,3875,3878,3880,3883,3885],{"class":2131,"line":2576},[2129,3874,2897],{"class":2135},[2129,3876,3877],{"class":2201}," count",[2129,3879,2206],{"class":2205},[2129,3881,3882],{"class":2135}," match",[2129,3884,3848],{"class":2201},[2129,3886,2905],{"class":2143},[2129,3888,3889,3892,3894,3897],{"class":2131,"line":2582},[2129,3890,3891],{"class":2139},"            Ok",[2129,3893,2217],{"class":2143},[2129,3895,3896],{"class":2201},"count",[2129,3898,3899],{"class":2143},") => {\n",[2129,3901,3902],{"class":2131,"line":2587},[2129,3903,3904],{"class":2201},"                count\n",[2129,3906,3907],{"class":2131,"line":2604},[2129,3908,3489],{"class":2143},[2129,3910,3911,3914,3916,3919],{"class":2131,"line":2610},[2129,3912,3913],{"class":2139},"            Err",[2129,3915,2217],{"class":2143},[2129,3917,3918],{"class":2201},"e",[2129,3920,3899],{"class":2143},[2129,3922,3923,3926,3929,3931,3933],{"class":2131,"line":2644},[2129,3924,3925],{"class":2135},"                return",[2129,3927,3928],{"class":2139}," Err",[2129,3930,2217],{"class":2143},[2129,3932,3918],{"class":2201},[2129,3934,2256],{"class":2143},[2129,3936,3937],{"class":2131,"line":2664},[2129,3938,3489],{"class":2143},[2129,3940,3941],{"class":2131,"line":3216},[2129,3942,2947],{"class":2143},[2129,3944,3945],{"class":2131,"line":1776},[2129,3946,2179],{"emptyLinePlaceholder":1766},[2129,3948,3949,3951,3953,3956,3958],{"class":2131,"line":3248},[2129,3950,2830],{"class":2135},[2129,3952,3877],{"class":2201},[2129,3954,3955],{"class":2205}," !=",[2129,3957,2659],{"class":2422},[2129,3959,2905],{"class":2143},[2129,3961,3962,3964,3966,3968,3970],{"class":2131,"line":3278},[2129,3963,2873],{"class":2135},[2129,3965,3827],{"class":2139},[2129,3967,2217],{"class":2143},[2129,3969,3896],{"class":2201},[2129,3971,2256],{"class":2143},[2129,3973,3974],{"class":2131,"line":3293},[2129,3975,2892],{"class":2143},[2129,3977,3978],{"class":2131,"line":3310},[2129,3979,2179],{"emptyLinePlaceholder":1766},[2129,3981,3982,3984,3986,3989,3991],{"class":2131,"line":3330},[2129,3983,2830],{"class":2135},[2129,3985,3877],{"class":2201},[2129,3987,3988],{"class":2205}," ==",[2129,3990,2659],{"class":2422},[2129,3992,2905],{"class":2143},[2129,3994,3995,3997,3999,4001,4003,4005,4008],{"class":2131,"line":3335},[2129,3996,2910],{"class":2135},[2129,3998,3796],{"class":2201},[2129,4000,2206],{"class":2205},[2129,4002,2776],{"class":2139},[2129,4004,3803],{"class":2143},[2129,4006,4007],{"class":2188},"take",[2129,4009,2230],{"class":2143},[2129,4011,4012,4014,4017,4019,4021,4024,4027,4029,4031],{"class":2131,"line":3359},[2129,4013,3338],{"class":2135},[2129,4015,4016],{"class":2135}," let",[2129,4018,3507],{"class":2139},[2129,4020,2217],{"class":2143},[2129,4022,4023],{"class":2201},"decoder",[2129,4025,4026],{"class":2143},") ",[2129,4028,2511],{"class":2205},[2129,4030,3796],{"class":2201},[2129,4032,2905],{"class":2143},[2129,4034,4035,4038,4040,4043,4045,4047,4049,4052],{"class":2131,"line":3374},[2129,4036,4037],{"class":2135},"                let",[2129,4039,2264],{"class":2135},[2129,4041,4042],{"class":2201}," reader",[2129,4044,2206],{"class":2205},[2129,4046,3796],{"class":2201},[2129,4048,178],{"class":2143},[2129,4050,4051],{"class":2188},"into_inner",[2129,4053,2230],{"class":2143},[2129,4055,4056],{"class":2131,"line":3379},[2129,4057,4058],{"class":2398},"                \u002F\u002F S'il reste encore des octets à lire dans reader alors on continue, sinon on s'arrête\n",[2129,4060,4061,4063,4065,4067,4069,4072,4074],{"class":2131,"line":3415},[2129,4062,3382],{"class":2135},[2129,4064,4042],{"class":2201},[2129,4066,178],{"class":2143},[2129,4068,2712],{"class":2188},[2129,4070,4071],{"class":2143},"()?.",[2129,4073,3354],{"class":2188},[2129,4075,2192],{"class":2143},[2129,4077,4078,4081,4083,4085,4087],{"class":2131,"line":3434},[2129,4079,4080],{"class":2135},"                    return",[2129,4082,3827],{"class":2139},[2129,4084,2217],{"class":2143},[2129,4086,2423],{"class":2422},[2129,4088,2256],{"class":2143},[2129,4090,4091],{"class":2131,"line":3459},[2129,4092,3483],{"class":2143},[2129,4094,4095,4098,4100,4103],{"class":2131,"line":3465},[2129,4096,4097],{"class":2201},"                reader",[2129,4099,178],{"class":2143},[2129,4101,4102],{"class":2188},"reset",[2129,4104,2230],{"class":2143},[2129,4106,4107],{"class":2131,"line":3480},[2129,4108,2179],{"emptyLinePlaceholder":1766},[2129,4110,4111,4113,4116,4118,4120,4122,4124,4126,4128,4130,4133],{"class":2131,"line":3486},[2129,4112,3362],{"class":2139},[2129,4114,4115],{"class":2143},".decoder ",[2129,4117,2511],{"class":2205},[2129,4119,3507],{"class":2139},[2129,4121,2217],{"class":2143},[2129,4123,2152],{"class":2139},[2129,4125,2144],{"class":2143},[2129,4127,2248],{"class":2188},[2129,4129,2217],{"class":2143},[2129,4131,4132],{"class":2201},"reader",[2129,4134,4135],{"class":2143},"));\n",[2129,4137,4138],{"class":2131,"line":3492},[2129,4139,3489],{"class":2143},[2129,4141,4142],{"class":2131,"line":3497},[2129,4143,2892],{"class":2143},[2129,4145,4146],{"class":2131,"line":3516},[2129,4147,2980],{"class":2143},[2129,4149,4150],{"class":2131,"line":3521},[2129,4151,2353],{"class":2143},[11,4153,4154,4155,4157],{},"J'ai donc rédigé une méthode ",[183,4156,2881],{}," qui remplit le tampon tant que le nombre d'octets lus est inférieur à la taille du\ntampon. On ne retourne 0 que dans le cas où la lecture du fichier est terminée.",[2122,4159,4161],{"className":2124,"code":4160,"language":1774,"meta":1645,"style":1645},"fn read(&mut self, buf: &mut [u8]) -> io::Result\u003Cusize> {\n    let mut total_bytes_read = 0;\n\n    while total_bytes_read \u003C buf.len() {\n        let bytes_to_read = &mut buf[total_bytes_read..];\n        let bytes_read = self.read_some_bytes(bytes_to_read)?;\n        total_bytes_read += bytes_read;\n\n        if bytes_read == 0 {\n            break;\n        }\n    }\n\n    Ok(total_bytes_read)\n}\n",[183,4162,4163,4201,4216,4220,4238,4262,4285,4297,4301,4313,4320,4324,4328,4332,4343],{"__ignoreMap":1645},[2129,4164,4165,4167,4169,4171,4173,4175,4177,4179,4181,4183,4185,4187,4189,4191,4193,4195,4197,4199],{"class":2131,"line":2132},[2129,4166,2185],{"class":2135},[2129,4168,2769],{"class":2188},[2129,4170,2318],{"class":2143},[2129,4172,2321],{"class":2135},[2129,4174,2776],{"class":2139},[2129,4176,931],{"class":2143},[2129,4178,2781],{"class":2201},[2129,4180,2784],{"class":2143},[2129,4182,2321],{"class":2135},[2129,4184,2789],{"class":2143},[2129,4186,2792],{"class":2139},[2129,4188,2795],{"class":2143},[2129,4190,2798],{"class":2139},[2129,4192,2144],{"class":2143},[2129,4194,2803],{"class":2139},[2129,4196,2735],{"class":2143},[2129,4198,2808],{"class":2139},[2129,4200,2761],{"class":2143},[2129,4202,4203,4205,4207,4210,4212,4214],{"class":2131,"line":1646},[2129,4204,2198],{"class":2135},[2129,4206,2264],{"class":2135},[2129,4208,4209],{"class":2201}," total_bytes_read",[2129,4211,2206],{"class":2205},[2129,4213,2659],{"class":2422},[2129,4215,2155],{"class":2143},[2129,4217,4218],{"class":2131,"line":1651},[2129,4219,2179],{"emptyLinePlaceholder":1766},[2129,4221,4222,4225,4227,4230,4232,4234,4236],{"class":2131,"line":2182},[2129,4223,4224],{"class":2135},"    while",[2129,4226,4209],{"class":2201},[2129,4228,4229],{"class":2205}," \u003C",[2129,4231,2848],{"class":2201},[2129,4233,178],{"class":2143},[2129,4235,2853],{"class":2188},[2129,4237,2192],{"class":2143},[2129,4239,4240,4242,4245,4247,4250,4252,4254,4256,4259],{"class":2131,"line":2195},[2129,4241,2897],{"class":2135},[2129,4243,4244],{"class":2201}," bytes_to_read",[2129,4246,2206],{"class":2205},[2129,4248,4249],{"class":2143}," &",[2129,4251,2321],{"class":2135},[2129,4253,2848],{"class":2201},[2129,4255,2419],{"class":2143},[2129,4257,4258],{"class":2201},"total_bytes_read",[2129,4260,4261],{"class":2143},"..];\n",[2129,4263,4264,4266,4269,4271,4273,4275,4277,4279,4282],{"class":2131,"line":2233},[2129,4265,2897],{"class":2135},[2129,4267,4268],{"class":2201}," bytes_read",[2129,4270,2206],{"class":2205},[2129,4272,2776],{"class":2139},[2129,4274,178],{"class":2143},[2129,4276,3677],{"class":2188},[2129,4278,2217],{"class":2143},[2129,4280,4281],{"class":2201},"bytes_to_read",[2129,4283,4284],{"class":2143},")?;\n",[2129,4286,4287,4290,4293,4295],{"class":2131,"line":2259},[2129,4288,4289],{"class":2201},"        total_bytes_read",[2129,4291,4292],{"class":2205}," +=",[2129,4294,4268],{"class":2201},[2129,4296,2155],{"class":2143},[2129,4298,4299],{"class":2131,"line":2286},[2129,4300,2179],{"emptyLinePlaceholder":1766},[2129,4302,4303,4305,4307,4309,4311],{"class":2131,"line":2307},[2129,4304,2830],{"class":2135},[2129,4306,4268],{"class":2201},[2129,4308,3988],{"class":2205},[2129,4310,2659],{"class":2422},[2129,4312,2905],{"class":2143},[2129,4314,4315,4318],{"class":2131,"line":2332},[2129,4316,4317],{"class":2135},"            break",[2129,4319,2155],{"class":2143},[2129,4321,4322],{"class":2131,"line":2350},[2129,4323,2892],{"class":2143},[2129,4325,4326],{"class":2131,"line":2564},[2129,4327,2980],{"class":2143},[2129,4329,4330],{"class":2131,"line":2570},[2129,4331,2179],{"emptyLinePlaceholder":1766},[2129,4333,4334,4337,4339,4341],{"class":2131,"line":2576},[2129,4335,4336],{"class":2139},"    Ok",[2129,4338,2217],{"class":2143},[2129,4340,4258],{"class":2201},[2129,4342,2975],{"class":2143},[2129,4344,4345],{"class":2131,"line":2582},[2129,4346,2353],{"class":2143},[132,4348,4350],{"id":4349},"les-fichiers-dattributs","Les fichiers d'attributs",[11,4352,4353],{},"Les fichiers d'attributs sont d'abord des fichiers compressés en utilisant la méthode décrite précédemment. J'ai donc\nutilisé le Reader que j'ai écrit précédemment pour décompresser les fichiers au fur et à mesure de leur lecture.",[11,4355,4356],{},"Je me suis ensuite attaqué au décodage des fichiers d'attributs. En réécrivant le code, j'ai découvert que certains\nnombres étaient mal encodés, ce qui provoquait des erreurs de décodage que j'ai dû gérer avec plus de flexibilité.",[11,4358,4359,4360,4362,4363,4365,4366,4371],{},"Pour lire un ",[183,4361,2046],{},", j'ai ajouté un trait au trait ",[183,4364,2744],{},". Je me suis basé sur le ",[48,4367,4370],{"href":4368,"rel":4369},"https:\u002F\u002Fgithub.com\u002Fbackuppc\u002Fbackuppc-xs\u002Fblob\u002Fmaster\u002Fbpc_attrib.c#L548",[346],"code C de BackupPC",",\ntout en modifiant la philosophie pour répondre au fonctionnement d'un Reader Rust.",[2122,4373,4375],{"className":2124,"code":4374,"language":1774,"meta":1645,"style":1645},"pub trait VarintRead: Read {\n    fn read_varint(&mut self) -> io::Result\u003Cu64> {\n        let mut result = 0;\n        let mut shift = 0;\n\n        loop {\n            let mut buf: [u8; 1] = [0u8; 1];\n            self.read_exact(&mut buf)?;\n\n            let byte = buf[0];\n            let val = (byte & 0x7F) as u64;\n            if shift >= 64 || val \u003C\u003C shift >> shift != val {\n                eprintln!(\"Varint too large: probably corrupted data\");\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    \"Varint too large: probably corrupted data\",\n                ));\n            }\n\n            result |= val \u003C\u003C shift;\n            if byte & 0x80 == 0 {\n                return Ok(result);\n            }\n            shift += 7;\n        }\n    }\n}\n",[183,4376,4377,4393,4421,4436,4451,4455,4462,4497,4514,4518,4535,4566,4598,4610,4632,4649,4656,4661,4665,4669,4685,4702,4715,4719,4731,4735,4739],{"__ignoreMap":1645},[2129,4378,4379,4381,4384,4387,4389,4391],{"class":2131,"line":2132},[2129,4380,3691],{"class":2135},[2129,4382,4383],{"class":2135}," trait",[2129,4385,4386],{"class":2139}," VarintRead",[2129,4388,2741],{"class":2143},[2129,4390,2744],{"class":2139},[2129,4392,2905],{"class":2143},[2129,4394,4395,4397,4400,4402,4404,4406,4408,4410,4412,4414,4416,4419],{"class":2131,"line":1646},[2129,4396,2766],{"class":2135},[2129,4398,4399],{"class":2188}," read_varint",[2129,4401,2318],{"class":2143},[2129,4403,2321],{"class":2135},[2129,4405,2776],{"class":2139},[2129,4407,3106],{"class":2143},[2129,4409,2798],{"class":2139},[2129,4411,2144],{"class":2143},[2129,4413,2803],{"class":2139},[2129,4415,2735],{"class":2143},[2129,4417,4418],{"class":2139},"u64",[2129,4420,2761],{"class":2143},[2129,4422,4423,4425,4427,4430,4432,4434],{"class":2131,"line":1651},[2129,4424,2897],{"class":2135},[2129,4426,2264],{"class":2135},[2129,4428,4429],{"class":2201}," result",[2129,4431,2206],{"class":2205},[2129,4433,2659],{"class":2422},[2129,4435,2155],{"class":2143},[2129,4437,4438,4440,4442,4445,4447,4449],{"class":2131,"line":2182},[2129,4439,2897],{"class":2135},[2129,4441,2264],{"class":2135},[2129,4443,4444],{"class":2201}," shift",[2129,4446,2206],{"class":2205},[2129,4448,2659],{"class":2422},[2129,4450,2155],{"class":2143},[2129,4452,4453],{"class":2131,"line":2195},[2129,4454,2179],{"emptyLinePlaceholder":1766},[2129,4456,4457,4460],{"class":2131,"line":2233},[2129,4458,4459],{"class":2135},"        loop",[2129,4461,2905],{"class":2143},[2129,4463,4464,4466,4468,4470,4473,4475,4478,4480,4482,4484,4486,4488,4490,4492,4494],{"class":2131,"line":2259},[2129,4465,2910],{"class":2135},[2129,4467,2264],{"class":2135},[2129,4469,2848],{"class":2201},[2129,4471,4472],{"class":2143},": [",[2129,4474,2792],{"class":2139},[2129,4476,4477],{"class":2143},"; ",[2129,4479,1952],{"class":2422},[2129,4481,2426],{"class":2143},[2129,4483,2511],{"class":2205},[2129,4485,2789],{"class":2143},[2129,4487,2423],{"class":2422},[2129,4489,2792],{"class":2139},[2129,4491,4477],{"class":2143},[2129,4493,1952],{"class":2422},[2129,4495,4496],{"class":2143},"];\n",[2129,4498,4499,4501,4503,4506,4508,4510,4512],{"class":2131,"line":2286},[2129,4500,3500],{"class":2139},[2129,4502,178],{"class":2143},[2129,4504,4505],{"class":2188},"read_exact",[2129,4507,2318],{"class":2143},[2129,4509,2321],{"class":2135},[2129,4511,2848],{"class":2201},[2129,4513,4284],{"class":2143},[2129,4515,4516],{"class":2131,"line":2307},[2129,4517,2179],{"emptyLinePlaceholder":1766},[2129,4519,4520,4522,4525,4527,4529,4531,4533],{"class":2131,"line":2332},[2129,4521,2910],{"class":2135},[2129,4523,4524],{"class":2201}," byte",[2129,4526,2206],{"class":2205},[2129,4528,2848],{"class":2201},[2129,4530,2419],{"class":2143},[2129,4532,2423],{"class":2422},[2129,4534,4496],{"class":2143},[2129,4536,4537,4539,4542,4544,4547,4550,4553,4556,4558,4561,4564],{"class":2131,"line":2350},[2129,4538,2910],{"class":2135},[2129,4540,4541],{"class":2201}," val",[2129,4543,2206],{"class":2205},[2129,4545,4546],{"class":2143}," (",[2129,4548,4549],{"class":2201},"byte",[2129,4551,4552],{"class":2143}," & ",[2129,4554,4555],{"class":2422},"0x7F",[2129,4557,4026],{"class":2143},[2129,4559,4560],{"class":2135},"as",[2129,4562,4563],{"class":2139}," u64",[2129,4565,2155],{"class":2143},[2129,4567,4568,4570,4572,4575,4578,4580,4582,4585,4587,4590,4592,4594,4596],{"class":2131,"line":2564},[2129,4569,3338],{"class":2135},[2129,4571,4444],{"class":2201},[2129,4573,4574],{"class":2205}," >=",[2129,4576,4577],{"class":2422}," 64",[2129,4579,2438],{"class":2205},[2129,4581,4541],{"class":2201},[2129,4583,4584],{"class":2205}," \u003C\u003C",[2129,4586,4444],{"class":2201},[2129,4588,4589],{"class":2205}," >>",[2129,4591,4444],{"class":2201},[2129,4593,3955],{"class":2205},[2129,4595,4541],{"class":2201},[2129,4597,2905],{"class":2143},[2129,4599,4600,4603,4605,4608],{"class":2131,"line":2570},[2129,4601,4602],{"class":2188},"                eprintln!",[2129,4604,2217],{"class":2143},[2129,4606,4607],{"class":2220},"\"Varint too large: probably corrupted data\"",[2129,4609,2256],{"class":2143},[2129,4611,4612,4614,4616,4618,4620,4622,4625,4627,4629],{"class":2131,"line":2576},[2129,4613,3925],{"class":2135},[2129,4615,3928],{"class":2139},[2129,4617,2217],{"class":2143},[2129,4619,2798],{"class":2139},[2129,4621,2144],{"class":2143},[2129,4623,4624],{"class":2139},"Error",[2129,4626,2144],{"class":2143},[2129,4628,2248],{"class":2188},[2129,4630,4631],{"class":2143},"(\n",[2129,4633,4634,4637,4639,4642,4644,4647],{"class":2131,"line":2582},[2129,4635,4636],{"class":2139},"                    io",[2129,4638,2144],{"class":2143},[2129,4640,4641],{"class":2139},"ErrorKind",[2129,4643,2144],{"class":2143},[2129,4645,4646],{"class":2139},"InvalidData",[2129,4648,3022],{"class":2143},[2129,4650,4651,4654],{"class":2131,"line":2587},[2129,4652,4653],{"class":2220},"                    \"Varint too large: probably corrupted data\"",[2129,4655,3022],{"class":2143},[2129,4657,4658],{"class":2131,"line":2604},[2129,4659,4660],{"class":2143},"                ));\n",[2129,4662,4663],{"class":2131,"line":2610},[2129,4664,3489],{"class":2143},[2129,4666,4667],{"class":2131,"line":2644},[2129,4668,2179],{"emptyLinePlaceholder":1766},[2129,4670,4671,4674,4677,4679,4681,4683],{"class":2131,"line":2664},[2129,4672,4673],{"class":2201},"            result",[2129,4675,4676],{"class":2205}," |=",[2129,4678,4541],{"class":2201},[2129,4680,4584],{"class":2205},[2129,4682,4444],{"class":2201},[2129,4684,2155],{"class":2143},[2129,4686,4687,4689,4691,4693,4696,4698,4700],{"class":2131,"line":3216},[2129,4688,3338],{"class":2135},[2129,4690,4524],{"class":2201},[2129,4692,4552],{"class":2143},[2129,4694,4695],{"class":2422},"0x80",[2129,4697,3988],{"class":2205},[2129,4699,2659],{"class":2422},[2129,4701,2905],{"class":2143},[2129,4703,4704,4706,4708,4710,4713],{"class":2131,"line":1776},[2129,4705,3925],{"class":2135},[2129,4707,3827],{"class":2139},[2129,4709,2217],{"class":2143},[2129,4711,4712],{"class":2201},"result",[2129,4714,2256],{"class":2143},[2129,4716,4717],{"class":2131,"line":3248},[2129,4718,3489],{"class":2143},[2129,4720,4721,4724,4726,4729],{"class":2131,"line":3278},[2129,4722,4723],{"class":2201},"            shift",[2129,4725,4292],{"class":2205},[2129,4727,4728],{"class":2422}," 7",[2129,4730,2155],{"class":2143},[2129,4732,4733],{"class":2131,"line":3293},[2129,4734,2892],{"class":2143},[2129,4736,4737],{"class":2131,"line":3310},[2129,4738,2980],{"class":2143},[2129,4740,4741],{"class":2131,"line":3330},[2129,4742,2353],{"class":2143},[11,4744,4745,4746,4749],{},"Au cours de mes tests pour lire les fichiers d'attributs du pool, j'ai rencontré de nombreuses erreurs de décodage (avec\nle message ci-dessus : ",[361,4747,4748],{},"Varint too large: probably corrupted data","). Ce qui est rassurant, c'est que cela n'est pas lié\nà mon algorithme. BackupPC rencontre le même problème lors du décodage de ses propres fichiers (ce qui se traduit dans\nson cas par une date en janvier 1970) :",[11,4751,4752],{},[120,4753],{"src":4754,"alt":4755,"className":4756},"\u002FWoodstock\u002F2024-03-14_pr_backuppc_pool_1.png","Fichier corrompu",[125],[11,4758,4759],{},"La question est donc : mes données sont-elles corrompues ou s'agit-il d'un bug de BackupPC ? Je ne vais pas obtenir de\nréponse à cette question.",[132,4761,4763],{"id":4762},"la-lecture-des-machines-et-des-sauvegardes","La lecture des machines et des sauvegardes",[11,4765,4766],{},"La partie la plus ardue est terminée.",[505,4768,4769,4774,4780],{},[369,4770,4771,4772,178],{},"Pour lister les machines, il suffit de lister les dossiers du répertoire ",[183,4773,2026],{},[369,4775,4776,4777,178],{},"Pour lister les sauvegardes, il faut lire le fichier texte (tsv) ",[183,4778,4779],{},"__TOPDIR__\u002Fpc\u002F\u003Cmachine>\u002Fbackups",[369,4781,4782,4783,178],{},"Pour lister les dossiers partagés, il faut lister les dossiers du répertoire ",[183,4784,4785],{},"__TOPDIR__\u002Fpc\u002F\u003Cmachine>\u002F\u003Cbackup>",[11,4787,4788,4789,178],{},"J'ai écrit ces trois méthodes dans ce fichier, que je vous invite à consulter. Il n'est pas très complexe : ",[48,4790,4793],{"href":4791,"rel":4792},"https:\u002F\u002Fgogs.shadoware.org\u002Fphoenix\u002Fbackuppc_pool\u002Fsrc\u002Fbranch\u002Fmaster\u002Fsrc\u002Fhosts.rs",[346],"hosts.rs",[11,4795,4796],{},"Je peux donc affirmer que j'ai réussi à lire les fichiers du pool de BackupPC. Pour mieux visualiser le résultat, j'ai\ndécidé de me lancer un défi supplémentaire : écrire un pilote FUSE pour visualiser le pool de stockage.",[132,4798,4800],{"id":4799},"le-driver-fuse","Le driver FUSE",[11,4802,4803],{},"L'étape suivante consiste donc à développer un pilote FUSE qui intègre tout cela. Il s'agit de lire les fichiers\nd'attributs pour reconstituer la structure du système de fichiers (qui sont dans le pool et compressés), et de lire les\nfichiers compressés pour les données.",[11,4805,4806,4807,178],{},"Pour gérer la partie du système de fichiers, j'ai utilisé la bibliothèque ",[48,4808,4811],{"href":4809,"rel":4810},"https:\u002F\u002Fdocs.rs\u002Ffuser\u002Flatest\u002Ffuser\u002Findex.html",[346],"fuser",[11,4813,4814],{},"Le cœur du système ayant été développé, la partie système de fichiers consiste principalement à décoder le chemin fourni\npar FUSE, à décompressé et lire le fichier d'attributs. Dans le cas où l'utilisateur souhaite lire le contenu d'un\nfichier, il faut décompresser le fichier compressé.",[11,4816,4817],{},"J'ai donc la partie système de fichiers qui gère les requêtes FUSE et les transmet à la partie vue (avec un système de\ncache), et la partie vue qui prend un chemin et le transforme dans le format suivant en appelant les différentes\nméthodes précédentes :",[505,4819,4820,4823,4826,4829],{},[369,4821,4822],{},"nom de l'hôte",[369,4824,4825],{},"numéro de sauvegarde",[369,4827,4828],{},"chemin du partage",[369,4830,4831],{},"chemin",[11,4833,4834,4835,4840],{},"Le décodage est réalisé dans le fichier ",[48,4836,4839],{"href":4837,"rel":4838},"https:\u002F\u002Fgogs.shadoware.org\u002Fphoenix\u002Fbackuppc_pool\u002Fsrc\u002Fbranch\u002Fmaster\u002Fsrc\u002Fview.rs",[346],"view.rs",".\nPour cette partie, j'ai ajouté des tests unitaires. Ces tests unitaires m'ont permis d'itérer plus rapidement pour\nconstruire le système de fichiers (sans avoir à monter ce dernier à chaque fois).",[11,4842,4843,4844,4847,4848,4851],{},"On appelle la méthode avec le chemin découpé à chaque ",[183,4845,4846],{},"\u002F"," pour obtenir les différents éléments du chemin (sous forme de\n",[183,4849,4850],{},"&[&str]",").:",[2122,4853,4855],{"className":2124,"code":4854,"language":1774,"meta":1645,"style":1645},"pub fn list(&self, path: &[&str]) -> Result\u003CVec\u003CFileAttributes>> {\n    match path.len() {\n",[183,4856,4857,4898],{"__ignoreMap":1645},[2129,4858,4859,4861,4864,4867,4869,4871,4873,4876,4879,4882,4884,4886,4888,4890,4892,4895],{"class":2131,"line":2132},[2129,4860,3691],{"class":2135},[2129,4862,4863],{"class":2135}," fn",[2129,4865,4866],{"class":2188}," list",[2129,4868,2318],{"class":2143},[2129,4870,3533],{"class":2139},[2129,4872,931],{"class":2143},[2129,4874,4875],{"class":2201},"path",[2129,4877,4878],{"class":2143},": &[&",[2129,4880,4881],{"class":2139},"str",[2129,4883,2795],{"class":2143},[2129,4885,2803],{"class":2139},[2129,4887,2735],{"class":2143},[2129,4889,3049],{"class":2139},[2129,4891,2735],{"class":2143},[2129,4893,4894],{"class":2139},"FileAttributes",[2129,4896,4897],{"class":2143},">> {\n",[2129,4899,4900,4903,4906,4908,4910],{"class":2131,"line":1646},[2129,4901,4902],{"class":2135},"    match",[2129,4904,4905],{"class":2201}," path",[2129,4907,178],{"class":2143},[2129,4909,2853],{"class":2188},[2129,4911,2192],{"class":2143},[11,4913,4914],{},"On commence d'abord par examiner le nombre d'éléments dans le chemin. Si nous n'avons aucun élément, cela signifie que\nnous sommes à la racine et que nous souhaitons obtenir la liste des machines.",[2122,4916,4918],{"className":2124,"code":4917,"language":1774,"meta":1645,"style":1645},"        0 => {\n            let hosts = self.hosts.list_hosts()?;\n            Ok(hosts.into_iter().map(FileAttributes::from_host).collect())\n        }\n",[183,4919,4920,4928,4947,4982],{"__ignoreMap":1645},[2129,4921,4922,4925],{"class":2131,"line":2132},[2129,4923,4924],{"class":2422},"        0",[2129,4926,4927],{"class":2143}," => {\n",[2129,4929,4930,4932,4935,4937,4939,4942,4945],{"class":2131,"line":1646},[2129,4931,2910],{"class":2135},[2129,4933,4934],{"class":2201}," hosts",[2129,4936,2206],{"class":2205},[2129,4938,2776],{"class":2139},[2129,4940,4941],{"class":2143},".hosts.",[2129,4943,4944],{"class":2188},"list_hosts",[2129,4946,2926],{"class":2143},[2129,4948,4949,4951,4953,4956,4958,4961,4963,4966,4968,4970,4972,4975,4977,4980],{"class":2131,"line":1651},[2129,4950,3891],{"class":2139},[2129,4952,2217],{"class":2143},[2129,4954,4955],{"class":2201},"hosts",[2129,4957,178],{"class":2143},[2129,4959,4960],{"class":2188},"into_iter",[2129,4962,3541],{"class":2143},[2129,4964,4965],{"class":2188},"map",[2129,4967,2217],{"class":2143},[2129,4969,4894],{"class":2139},[2129,4971,2144],{"class":2143},[2129,4973,4974],{"class":2201},"from_host",[2129,4976,2224],{"class":2143},[2129,4978,4979],{"class":2188},"collect",[2129,4981,3546],{"class":2143},[2129,4983,4984],{"class":2131,"line":2182},[2129,4985,2892],{"class":2143},[11,4987,4988],{},"Si nous avons un seul élément, alors c'est le nom de la machine. Nous souhaitons donc récupérer la liste des sauvegardes\nassociées à cette machine.",[2122,4990,4992],{"className":2124,"code":4991,"language":1774,"meta":1645,"style":1645},"        1 => {\n            let backups = self.hosts.list_backups(path[0]);\n            match backups {\n                Ok(backups) => Ok(backups\n                    .into_iter()\n                    .map(|a| FileAttributes::from_backup(&a))\n                    .collect()),\n                Err(err) => {\n                    \u002F\u002F If the file isn't found, it's because we should return empty vec\n                    if let Some(io_err) = err.downcast_ref::\u003Cstd::io::Error>() {\n                        if io_err.kind() == std::io::ErrorKind::NotFound {\n                            Ok(Vec::new())\n                        } else {\n                            Err(err)\n                        }\n                    } else {\n                        Err(err)\n                    }\n                }\n            }\n        }\n",[183,4993,4994,5001,5028,5037,5057,5067,5097,5106,5118,5123,5166,5200,5215,5224,5235,5240,5249,5260,5265,5269,5273],{"__ignoreMap":1645},[2129,4995,4996,4999],{"class":2131,"line":2132},[2129,4997,4998],{"class":2422},"        1",[2129,5000,4927],{"class":2143},[2129,5002,5003,5005,5008,5010,5012,5014,5017,5019,5021,5023,5025],{"class":2131,"line":1646},[2129,5004,2910],{"class":2135},[2129,5006,5007],{"class":2201}," backups",[2129,5009,2206],{"class":2205},[2129,5011,2776],{"class":2139},[2129,5013,4941],{"class":2143},[2129,5015,5016],{"class":2188},"list_backups",[2129,5018,2217],{"class":2143},[2129,5020,4875],{"class":2201},[2129,5022,2419],{"class":2143},[2129,5024,2423],{"class":2422},[2129,5026,5027],{"class":2143},"]);\n",[2129,5029,5030,5033,5035],{"class":2131,"line":1651},[2129,5031,5032],{"class":2135},"            match",[2129,5034,5007],{"class":2201},[2129,5036,2905],{"class":2143},[2129,5038,5039,5042,5044,5046,5049,5052,5054],{"class":2131,"line":2182},[2129,5040,5041],{"class":2139},"                Ok",[2129,5043,2217],{"class":2143},[2129,5045,1946],{"class":2201},[2129,5047,5048],{"class":2143},") => ",[2129,5050,5051],{"class":2139},"Ok",[2129,5053,2217],{"class":2143},[2129,5055,5056],{"class":2201},"backups\n",[2129,5058,5059,5062,5064],{"class":2131,"line":2195},[2129,5060,5061],{"class":2143},"                    .",[2129,5063,4960],{"class":2188},[2129,5065,5066],{"class":2143},"()\n",[2129,5068,5069,5071,5073,5075,5078,5080,5082,5085,5087,5090,5092,5094],{"class":2131,"line":2233},[2129,5070,5061],{"class":2143},[2129,5072,4965],{"class":2188},[2129,5074,2217],{"class":2143},[2129,5076,5077],{"class":2205},"|",[2129,5079,48],{"class":2201},[2129,5081,5077],{"class":2205},[2129,5083,5084],{"class":2139}," FileAttributes",[2129,5086,2144],{"class":2143},[2129,5088,5089],{"class":2188},"from_backup",[2129,5091,2318],{"class":2143},[2129,5093,48],{"class":2201},[2129,5095,5096],{"class":2143},"))\n",[2129,5098,5099,5101,5103],{"class":2131,"line":2259},[2129,5100,5061],{"class":2143},[2129,5102,4979],{"class":2188},[2129,5104,5105],{"class":2143},"()),\n",[2129,5107,5108,5111,5113,5116],{"class":2131,"line":2286},[2129,5109,5110],{"class":2139},"                Err",[2129,5112,2217],{"class":2143},[2129,5114,5115],{"class":2201},"err",[2129,5117,3899],{"class":2143},[2129,5119,5120],{"class":2131,"line":2307},[2129,5121,5122],{"class":2398},"                    \u002F\u002F If the file isn't found, it's because we should return empty vec\n",[2129,5124,5125,5128,5130,5132,5134,5137,5139,5141,5144,5146,5149,5152,5155,5157,5159,5161,5163],{"class":2131,"line":2332},[2129,5126,5127],{"class":2135},"                    if",[2129,5129,4016],{"class":2135},[2129,5131,3507],{"class":2139},[2129,5133,2217],{"class":2143},[2129,5135,5136],{"class":2201},"io_err",[2129,5138,4026],{"class":2143},[2129,5140,2511],{"class":2205},[2129,5142,5143],{"class":2201}," err",[2129,5145,178],{"class":2143},[2129,5147,5148],{"class":2188},"downcast_ref",[2129,5150,5151],{"class":2143},"::\u003C",[2129,5153,5154],{"class":2139},"std",[2129,5156,2144],{"class":2143},[2129,5158,2798],{"class":2139},[2129,5160,2144],{"class":2143},[2129,5162,4624],{"class":2139},[2129,5164,5165],{"class":2143},">() {\n",[2129,5167,5168,5171,5174,5176,5179,5181,5183,5185,5187,5189,5191,5193,5195,5198],{"class":2131,"line":2350},[2129,5169,5170],{"class":2135},"                        if",[2129,5172,5173],{"class":2201}," io_err",[2129,5175,178],{"class":2143},[2129,5177,5178],{"class":2188},"kind",[2129,5180,2856],{"class":2143},[2129,5182,2429],{"class":2205},[2129,5184,2162],{"class":2139},[2129,5186,2144],{"class":2143},[2129,5188,2798],{"class":2139},[2129,5190,2144],{"class":2143},[2129,5192,4641],{"class":2139},[2129,5194,2144],{"class":2143},[2129,5196,5197],{"class":2139},"NotFound",[2129,5199,2905],{"class":2143},[2129,5201,5202,5205,5207,5209,5211,5213],{"class":2131,"line":2564},[2129,5203,5204],{"class":2139},"                            Ok",[2129,5206,2217],{"class":2143},[2129,5208,3049],{"class":2139},[2129,5210,2144],{"class":2143},[2129,5212,2248],{"class":2188},[2129,5214,3546],{"class":2143},[2129,5216,5217,5220,5222],{"class":2131,"line":2570},[2129,5218,5219],{"class":2143},"                        } ",[2129,5221,2526],{"class":2135},[2129,5223,2905],{"class":2143},[2129,5225,5226,5229,5231,5233],{"class":2131,"line":2576},[2129,5227,5228],{"class":2139},"                            Err",[2129,5230,2217],{"class":2143},[2129,5232,5115],{"class":2201},[2129,5234,2975],{"class":2143},[2129,5236,5237],{"class":2131,"line":2582},[2129,5238,5239],{"class":2143},"                        }\n",[2129,5241,5242,5245,5247],{"class":2131,"line":2587},[2129,5243,5244],{"class":2143},"                    } ",[2129,5246,2526],{"class":2135},[2129,5248,2905],{"class":2143},[2129,5250,5251,5254,5256,5258],{"class":2131,"line":2604},[2129,5252,5253],{"class":2139},"                        Err",[2129,5255,2217],{"class":2143},[2129,5257,5115],{"class":2201},[2129,5259,2975],{"class":2143},[2129,5261,5262],{"class":2131,"line":2610},[2129,5263,5264],{"class":2143},"                    }\n",[2129,5266,5267],{"class":2131,"line":2644},[2129,5268,3483],{"class":2143},[2129,5270,5271],{"class":2131,"line":2664},[2129,5272,3489],{"class":2143},[2129,5274,5275],{"class":2131,"line":3216},[2129,5276,2892],{"class":2143},[11,5278,5279,5280,5283,5284,5286],{},"Si nous avons plus de deux éléments, alors nous souhaitons retourner au moins le partage, voire les fichiers à\nsauvegarder. Le problème est que le nom du partage ",[183,5281,5282],{},"share"," peut lui-même contenir des ",[183,5285,4846],{},". Il peut donc être composé de\nplusieurs éléments.",[11,5288,5289,5290,5293],{},"Le but de la méthode ",[183,5291,5292],{},"list_shares_of"," est de retourner le partage si ce dernier est un partage valide, ainsi que la\npartie du chemin qui suit le partage.",[2122,5295,5297],{"className":2124,"code":5296,"language":1774,"meta":1645,"style":1645},"        _ => {\n            let (shares, selected_share, share_size) =\n                self.list_shares_of(path[0], path[1].parse::\u003Cu32>().unwrap_or(0), &path[2..])?;\n\n            let shares = shares.into_iter().map(FileAttributes::from_share).collect();\n",[183,5298,5299,5306,5330,5388,5392],{"__ignoreMap":1645},[2129,5300,5301,5304],{"class":2131,"line":2132},[2129,5302,5303],{"class":2201},"        _",[2129,5305,4927],{"class":2143},[2129,5307,5308,5310,5312,5315,5317,5320,5322,5325,5327],{"class":2131,"line":1646},[2129,5309,2910],{"class":2135},[2129,5311,4546],{"class":2143},[2129,5313,5314],{"class":2201},"shares",[2129,5316,931],{"class":2143},[2129,5318,5319],{"class":2201},"selected_share",[2129,5321,931],{"class":2143},[2129,5323,5324],{"class":2201},"share_size",[2129,5326,4026],{"class":2143},[2129,5328,5329],{"class":2205},"=\n",[2129,5331,5332,5334,5336,5338,5340,5342,5344,5346,5349,5351,5353,5355,5358,5361,5363,5366,5369,5372,5374,5376,5379,5381,5383,5385],{"class":2131,"line":1651},[2129,5333,3362],{"class":2139},[2129,5335,178],{"class":2143},[2129,5337,5292],{"class":2188},[2129,5339,2217],{"class":2143},[2129,5341,4875],{"class":2201},[2129,5343,2419],{"class":2143},[2129,5345,2423],{"class":2422},[2129,5347,5348],{"class":2143},"], ",[2129,5350,4875],{"class":2201},[2129,5352,2419],{"class":2143},[2129,5354,1952],{"class":2422},[2129,5356,5357],{"class":2143},"].",[2129,5359,5360],{"class":2188},"parse",[2129,5362,5151],{"class":2143},[2129,5364,5365],{"class":2139},"u32",[2129,5367,5368],{"class":2143},">().",[2129,5370,5371],{"class":2188},"unwrap_or",[2129,5373,2217],{"class":2143},[2129,5375,2423],{"class":2422},[2129,5377,5378],{"class":2143},"), &",[2129,5380,4875],{"class":2201},[2129,5382,2419],{"class":2143},[2129,5384,1958],{"class":2422},[2129,5386,5387],{"class":2143},"..])?;\n",[2129,5389,5390],{"class":2131,"line":2182},[2129,5391,2179],{"emptyLinePlaceholder":1766},[2129,5393,5394,5396,5399,5401,5403,5405,5407,5409,5411,5413,5415,5417,5420,5422,5424],{"class":2131,"line":2195},[2129,5395,2910],{"class":2135},[2129,5397,5398],{"class":2201}," shares",[2129,5400,2206],{"class":2205},[2129,5402,5398],{"class":2201},[2129,5404,178],{"class":2143},[2129,5406,4960],{"class":2188},[2129,5408,3541],{"class":2143},[2129,5410,4965],{"class":2188},[2129,5412,2217],{"class":2143},[2129,5414,4894],{"class":2139},[2129,5416,2144],{"class":2143},[2129,5418,5419],{"class":2201},"from_share",[2129,5421,2224],{"class":2143},[2129,5423,4979],{"class":2188},[2129,5425,2230],{"class":2143},[11,5427,5428,5429,5432],{},"Une fois que nous avons le nom de la machine, le numéro de la sauvegarde et le partage, nous pouvons appeler la méthode\n",[183,5430,5431],{},"list_file_from_dir"," pour récupérer les fichiers du sous-dossier du partage.",[2122,5434,5436],{"className":2124,"code":5435,"language":1774,"meta":1645,"style":1645},"            match selected_share {\n                None => Ok(shares),\n                Some(selected_share) => self.list_file_from_dir(\n                    path[0],\n                    path[1].parse::\u003Cu32>().unwrap_or(0),\n                    &selected_share,\n                    &path[(2 + share_size)..].join(\"\u002F\"),\n                ),\n            }\n        }\n    }\n}\n",[183,5437,5438,5447,5464,5483,5495,5521,5530,5559,5564,5568,5572,5576],{"__ignoreMap":1645},[2129,5439,5440,5442,5445],{"class":2131,"line":2132},[2129,5441,5032],{"class":2135},[2129,5443,5444],{"class":2201}," selected_share",[2129,5446,2905],{"class":2143},[2129,5448,5449,5452,5455,5457,5459,5461],{"class":2131,"line":1646},[2129,5450,5451],{"class":2139},"                None",[2129,5453,5454],{"class":2143}," => ",[2129,5456,5051],{"class":2139},[2129,5458,2217],{"class":2143},[2129,5460,5314],{"class":2201},[2129,5462,5463],{"class":2143},"),\n",[2129,5465,5466,5469,5471,5473,5475,5477,5479,5481],{"class":2131,"line":1651},[2129,5467,5468],{"class":2139},"                Some",[2129,5470,2217],{"class":2143},[2129,5472,5319],{"class":2201},[2129,5474,5048],{"class":2143},[2129,5476,3533],{"class":2139},[2129,5478,178],{"class":2143},[2129,5480,5431],{"class":2188},[2129,5482,4631],{"class":2143},[2129,5484,5485,5488,5490,5492],{"class":2131,"line":2182},[2129,5486,5487],{"class":2201},"                    path",[2129,5489,2419],{"class":2143},[2129,5491,2423],{"class":2422},[2129,5493,5494],{"class":2143},"],\n",[2129,5496,5497,5499,5501,5503,5505,5507,5509,5511,5513,5515,5517,5519],{"class":2131,"line":2195},[2129,5498,5487],{"class":2201},[2129,5500,2419],{"class":2143},[2129,5502,1952],{"class":2422},[2129,5504,5357],{"class":2143},[2129,5506,5360],{"class":2188},[2129,5508,5151],{"class":2143},[2129,5510,5365],{"class":2139},[2129,5512,5368],{"class":2143},[2129,5514,5371],{"class":2188},[2129,5516,2217],{"class":2143},[2129,5518,2423],{"class":2422},[2129,5520,5463],{"class":2143},[2129,5522,5523,5526,5528],{"class":2131,"line":2233},[2129,5524,5525],{"class":2143},"                    &",[2129,5527,5319],{"class":2201},[2129,5529,3022],{"class":2143},[2129,5531,5532,5534,5536,5539,5541,5544,5546,5549,5552,5554,5557],{"class":2131,"line":2259},[2129,5533,5525],{"class":2143},[2129,5535,4875],{"class":2201},[2129,5537,5538],{"class":2143},"[(",[2129,5540,1958],{"class":2422},[2129,5542,5543],{"class":2143}," + ",[2129,5545,5324],{"class":2201},[2129,5547,5548],{"class":2143},")..].",[2129,5550,5551],{"class":2188},"join",[2129,5553,2217],{"class":2143},[2129,5555,5556],{"class":2220},"\"\u002F\"",[2129,5558,5463],{"class":2143},[2129,5560,5561],{"class":2131,"line":2286},[2129,5562,5563],{"class":2143},"                ),\n",[2129,5565,5566],{"class":2131,"line":2307},[2129,5567,3489],{"class":2143},[2129,5569,5570],{"class":2131,"line":2332},[2129,5571,2892],{"class":2143},[2129,5573,5574],{"class":2131,"line":2350},[2129,5575,2980],{"class":2143},[2129,5577,5578],{"class":2131,"line":2564},[2129,5579,2353],{"class":2143},[11,5581,5582,5583,5588],{},"Enfin, pour la partie FUSE, je vous invite à consulter le fichier ",[48,5584,5587],{"href":5585,"rel":5586},"https:\u002F\u002Fgogs.shadoware.org\u002Fphoenix\u002Fbackuppc_pool\u002Fsrc\u002Fbranch\u002Fmaster\u002Fsrc\u002Ffilesystem.rs",[346],"filesystem.rs",".\nCe fichier fait simplement le lien entre la partie FUSE et la partie vue.",[11,5590,5591],{},"Sur la partie FUSE, j'ai ajouté un cache pour éviter de lire plusieurs fois le même fichier (d'attributs) dans le pool.",[11,5593,5594,5595,5598],{},"Une fois le système de fichiers construit, il était nécessaire de le tester avec des tests réels. Le premier test a\nconsisté à parcourir l'ensemble du système de fichiers pour vérifier si la partie attribut fonctionnait correctement.\nPour cela, j'ai utilisé le programme ",[183,5596,5597],{},"filelight",". Ce programme permet de parcourir le système de fichiers et de\nvisualiser la taille des fichiers.",[11,5600,5601],{},"Voici un exemple sur une partie de la sauvegarde :",[11,5603,5604],{},[120,5605],{"src":5606,"alt":5607,"className":5608},"\u002FWoodstock\u002Ffilelight_localhost.png","Filelight",[125],[11,5610,5611],{},"Enfin, pour tester la capacité du programme à lire tous les fichiers du système de fichiers, j'ai écrit un petit script\nshell.",[2122,5613,5617],{"className":5614,"code":5615,"language":5616,"meta":1645,"style":1645},"language-bash shiki shiki-themes one-dark-pro","#!\u002Fbin\u002Fbash\n\n# Fichiers de sortie\noutput_file=\"md5sums.txt\"\nerror_file=\"errors.txt\"\n\n# Parcourir tous les fichiers du système de fichiers\nfind \u002Fhome\u002Fphoenix\u002Ftmp\u002Ftest -type f -print0 | while IFS= read -r -d '' file; do\n    # Essayer de calculer le hash MD5 du fichier\n    if md5sum \"$file\" >> \"$output_file\"; then\n        echo \"Processed $file\"\n    else\n        # Si le fichier ne peut pas être lu, écrire le nom du fichier dans le fichier d'erreur\n        echo \"Failed to process $file\" >> \"$error_file\"\n    fi\ndone\n","bash",[183,5618,5619,5624,5628,5633,5643,5653,5657,5662,5708,5713,5745,5758,5763,5768,5788,5793],{"__ignoreMap":1645},[2129,5620,5621],{"class":2131,"line":2132},[2129,5622,5623],{"class":2398},"#!\u002Fbin\u002Fbash\n",[2129,5625,5626],{"class":2131,"line":1646},[2129,5627,2179],{"emptyLinePlaceholder":1766},[2129,5629,5630],{"class":2131,"line":1651},[2129,5631,5632],{"class":2398},"# Fichiers de sortie\n",[2129,5634,5635,5638,5640],{"class":2131,"line":2182},[2129,5636,5637],{"class":2201},"output_file",[2129,5639,2511],{"class":2205},[2129,5641,5642],{"class":2220},"\"md5sums.txt\"\n",[2129,5644,5645,5648,5650],{"class":2131,"line":2195},[2129,5646,5647],{"class":2201},"error_file",[2129,5649,2511],{"class":2205},[2129,5651,5652],{"class":2220},"\"errors.txt\"\n",[2129,5654,5655],{"class":2131,"line":2233},[2129,5656,2179],{"emptyLinePlaceholder":1766},[2129,5658,5659],{"class":2131,"line":2259},[2129,5660,5661],{"class":2398},"# Parcourir tous les fichiers du système de fichiers\n",[2129,5663,5664,5667,5670,5673,5675,5678,5681,5684,5687,5689,5691,5694,5697,5700,5703,5705],{"class":2131,"line":2286},[2129,5665,5666],{"class":2188},"find",[2129,5668,5669],{"class":2220}," \u002Fhome\u002Fphoenix\u002Ftmp\u002Ftest",[2129,5671,5672],{"class":2422}," -type",[2129,5674,2202],{"class":2220},[2129,5676,5677],{"class":2422}," -print0",[2129,5679,5680],{"class":2143}," | ",[2129,5682,5683],{"class":2135},"while",[2129,5685,5686],{"class":2201}," IFS",[2129,5688,2511],{"class":2205},[2129,5690,2769],{"class":2205},[2129,5692,5693],{"class":2422}," -r",[2129,5695,5696],{"class":2422}," -d",[2129,5698,5699],{"class":2220}," ''",[2129,5701,5702],{"class":2220}," file",[2129,5704,4477],{"class":2143},[2129,5706,5707],{"class":2135},"do\n",[2129,5709,5710],{"class":2131,"line":2307},[2129,5711,5712],{"class":2398},"    # Essayer de calculer le hash MD5 du fichier\n",[2129,5714,5715,5718,5721,5724,5727,5730,5733,5735,5738,5740,5742],{"class":2131,"line":2332},[2129,5716,5717],{"class":2135},"    if",[2129,5719,5720],{"class":2188}," md5sum",[2129,5722,5723],{"class":2220}," \"",[2129,5725,5726],{"class":2201},"$file",[2129,5728,5729],{"class":2220},"\"",[2129,5731,5732],{"class":2143}," >> ",[2129,5734,5729],{"class":2220},[2129,5736,5737],{"class":2201},"$output_file",[2129,5739,5729],{"class":2220},[2129,5741,4477],{"class":2143},[2129,5743,5744],{"class":2135},"then\n",[2129,5746,5747,5750,5753,5755],{"class":2131,"line":2350},[2129,5748,5749],{"class":2205},"        echo",[2129,5751,5752],{"class":2220}," \"Processed ",[2129,5754,5726],{"class":2201},[2129,5756,5757],{"class":2220},"\"\n",[2129,5759,5760],{"class":2131,"line":2564},[2129,5761,5762],{"class":2135},"    else\n",[2129,5764,5765],{"class":2131,"line":2570},[2129,5766,5767],{"class":2398},"        # Si le fichier ne peut pas être lu, écrire le nom du fichier dans le fichier d'erreur\n",[2129,5769,5770,5772,5775,5777,5779,5781,5783,5786],{"class":2131,"line":2576},[2129,5771,5749],{"class":2205},[2129,5773,5774],{"class":2220}," \"Failed to process ",[2129,5776,5726],{"class":2201},[2129,5778,5729],{"class":2220},[2129,5780,5732],{"class":2143},[2129,5782,5729],{"class":2220},[2129,5784,5785],{"class":2201},"$error_file",[2129,5787,5757],{"class":2220},[2129,5789,5790],{"class":2131,"line":2582},[2129,5791,5792],{"class":2135},"    fi\n",[2129,5794,5795],{"class":2131,"line":2587},[2129,5796,5797],{"class":2135},"done\n",[11,5799,5800],{},"J'ai récemment commencé à développer un autre programme en Rust qui utilise la notion de multithreading.",[132,5802,5804],{"id":5803},"les-avantages-de-rust","Les avantages de Rust",[11,5806,5807],{},"En travaillant sur ce petit programme Rust, j'ai apprécié plusieurs aspects du langage. Rust offre la possibilité\nd'écrire du code de bas niveau sans avoir à gérer la mémoire comme en C, ce qui m'a procuré un sentiment de sécurité. De\nplus, la documentation de Rust est bien conçue et m'a permis de progresser rapidement.",[11,5809,5810,5811,5816],{},"J'ai également trouvé la syntaxe de Rust très lisible, à condition de ne pas utiliser les durées de vie. La présence de\nnombreux packages (crates) sur ",[48,5812,5815],{"href":5813,"rel":5814},"https:\u002F\u002Fcrates.io\u002F",[346],"crates.io"," facilite le développement rapide de fonctionnalités.",[11,5818,5819],{},"Clippy, un outil de vérification du code Rust, m'a été très utile. Il donne des conseils pour améliorer le code, ce qui\nm'a aidé à progresser, même s'il me reste encore du chemin à parcourir.",[132,5821,5823],{"id":5822},"les-défis-rencontrés-avec-rust","Les défis rencontrés avec Rust",[11,5825,5826,5827,5830],{},"Malgré ces avantages, j'ai rencontré quelques difficultés, probablement dues à ma méconnaissance du langage Rust. Par\nexemple, pour certaines parties du code, je me suis retrouvé avec du code contenant le mot ",[361,5828,5829],{},"unsafe",". J'ai refusé de\nl'utiliser, ce qui a rendu mon code plus complexe.",[11,5832,5833,5834,5837,5838,5840],{},"J'ai un cas particulier pour lequel je n'ai pas trouvé de solution. Dans mon logiciel de sauvegarde, je lis des noms de\nfichier que je stocke en tant que ",[183,5835,5836],{},"[u8]"," en Protobuf dans un fichier. Si je traite ces noms de fichier en tant que\nchaîne de caractères, je me retrouve avec des erreurs potentielles de conversion si le nom de fichier n'est pas en UTF-8\nsous Linux et en UTF-16 sous Windows. Pour stocker donc sous forme d'octet, j'utilise ces méthodes qui m'obligent à\nutiliser ",[183,5839,5829],{}," :",[2122,5842,5844],{"className":2124,"code":5843,"language":1774,"meta":1645,"style":1645},"#[must_use]\npub fn osstr_to_vec(path: &OsStr) -> Vec\u003Cu8> {\n    path.as_encoded_bytes().to_vec()\n}\n\n#[must_use]\npub fn vec_to_osstr(vec: &[u8]) -> OsString {\n    unsafe { OsString::from_encoded_bytes_unchecked(vec.to_owned()) }\n}\n",[183,5845,5846,5851,5879,5895,5899,5903,5907,5933,5960],{"__ignoreMap":1645},[2129,5847,5848],{"class":2131,"line":2132},[2129,5849,5850],{"class":2143},"#[must_use]\n",[2129,5852,5853,5855,5857,5860,5862,5864,5866,5869,5871,5873,5875,5877],{"class":2131,"line":1646},[2129,5854,3691],{"class":2135},[2129,5856,4863],{"class":2135},[2129,5858,5859],{"class":2188}," osstr_to_vec",[2129,5861,2217],{"class":2143},[2129,5863,4875],{"class":2201},[2129,5865,2784],{"class":2143},[2129,5867,5868],{"class":2139},"OsStr",[2129,5870,3106],{"class":2143},[2129,5872,3049],{"class":2139},[2129,5874,2735],{"class":2143},[2129,5876,2792],{"class":2139},[2129,5878,2761],{"class":2143},[2129,5880,5881,5884,5886,5889,5891,5893],{"class":2131,"line":1651},[2129,5882,5883],{"class":2201},"    path",[2129,5885,178],{"class":2143},[2129,5887,5888],{"class":2188},"as_encoded_bytes",[2129,5890,3541],{"class":2143},[2129,5892,3325],{"class":2188},[2129,5894,5066],{"class":2143},[2129,5896,5897],{"class":2131,"line":2182},[2129,5898,2353],{"class":2143},[2129,5900,5901],{"class":2131,"line":2195},[2129,5902,2179],{"emptyLinePlaceholder":1766},[2129,5904,5905],{"class":2131,"line":2233},[2129,5906,5850],{"class":2143},[2129,5908,5909,5911,5913,5916,5918,5921,5924,5926,5928,5931],{"class":2131,"line":2259},[2129,5910,3691],{"class":2135},[2129,5912,4863],{"class":2135},[2129,5914,5915],{"class":2188}," vec_to_osstr",[2129,5917,2217],{"class":2143},[2129,5919,5920],{"class":2201},"vec",[2129,5922,5923],{"class":2143},": &[",[2129,5925,2792],{"class":2139},[2129,5927,2795],{"class":2143},[2129,5929,5930],{"class":2139},"OsString",[2129,5932,2905],{"class":2143},[2129,5934,5935,5938,5941,5943,5945,5948,5950,5952,5954,5957],{"class":2131,"line":2286},[2129,5936,5937],{"class":2135},"    unsafe",[2129,5939,5940],{"class":2143}," { ",[2129,5942,5930],{"class":2139},[2129,5944,2144],{"class":2143},[2129,5946,5947],{"class":2188},"from_encoded_bytes_unchecked",[2129,5949,2217],{"class":2143},[2129,5951,5920],{"class":2201},[2129,5953,178],{"class":2143},[2129,5955,5956],{"class":2188},"to_owned",[2129,5958,5959],{"class":2143},"()) }\n",[2129,5961,5962],{"class":2131,"line":2307},[2129,5963,2353],{"class":2143},[11,5965,5966,5967,5969],{},"Le problème est que dans l'interface, des noms Windows ou Linux peuvent être affichés. Je ne souhaite pas que le\nprogramme plante, mais qu'au pire des cas, il affiche un nom de fichier incorrect. Je ne suis pas sûr que le code écrit\nsoit la meilleure solution. (D'après mes tests, il fonctionne). Une meilleure solution pour convertir un ",[183,5968,5868],{}," en\nString en fonction de l'encodage actuel (la variable d'environnement LANG sous Linux) aurait été pratique.",[11,5971,5972],{},"La gestion de la mémoire, bien que semblant simple au premier abord, devient rapidement complexe lorsqu'on veut faire du\nmultithreading. On se retrouve à mettre des structures dans des Box, des Mutex et des Arc, ce qui complique la syntaxe.",[11,5974,5975,5976,5979,5980,5983],{},"L'introduction de dynamisme, notamment pour la gestion des erreurs, nécessite l'utilisation du mot-clé ",[183,5977,5978],{},"dyn",", ce qui\npeut également devenir complexe. Je me retrouve avec presque toutes mes méthodes qui retournent un\n",[183,5981,5982],{},"Result\u003C???, Box\u003Cdyn Error>>",". Là aussi, je me demande si c'est une bonne pratique.",[11,5985,5986],{},"De plus, Rust semble souvent nous obliger à cloner des objets, alors que j'aurais parfois préféré utiliser simplement la\nréférence de l'objet. Mais comme potentiellement Rust n'arrive pas à déterminer si dans un cas de multithreading la\nvariable sera toujours valide, on doit la cloner. Parfois dans ces cas, je me dis qu'en C, je sais quand j'alloue, quand\nje désalloue, qui a le droit de lire et d'écrire. De mes souvenirs du C et du C++, j'avais plus de contrôle, le code me\nparaissait plus facile à écrire. Maintenant, le moindre oubli, petite erreur, peut être fatal, et Rust nous protège de\ncela. Bref, je pense simplement que je dois encore prendre le temps de me familiariser avec la philosophie de Rust.",[11,5988,5989,5990,931,5993,931,5996,938,5999,6002],{},"Enfin, les erreurs de compilation ont été un gros défi. J'ai passé des heures à essayer de comprendre certaines erreurs,\nà ajuster les durées de vie ou à ajouter ",[183,5991,5992],{},"static",[183,5994,5995],{},"Send",[183,5997,5998],{},"Sync",[183,6000,6001],{},"Clone"," sans savoir quelle était la bonne façon de\nfaire.",[132,6004,6006],{"id":6005},"les-tests-unitaires","Les tests unitaires",[11,6008,6009],{},"J'ai réalisé des tests unitaires pour une partie du développement. Cependant, l'écriture de ces tests a été difficile,\nsurtout en comparaison avec des bibliothèques comme Jest en JavaScript.",[11,6011,6012,6013,178],{},"La création de mocks pour les interfaces et les autres fichiers n'est pas native à Rust et n'est pas une tâche simple.\nJ'ai dû utiliser une bibliothèque dédiée, ",[48,6014,6017],{"href":6015,"rel":6016},"https:\u002F\u002Fdocs.rs\u002Fmockall\u002Flatest\u002Fmockall\u002F",[346],"mockall",[11,6019,6020,6021,178],{},"L'utilisation de mocks m'a contraint à créer des structures, alors qu'initialement j'avais des méthodes. J'ai commencé\navec des méthodes statiques, mais leur gestion avec les mocks ne permet pas de réaliser des tests unitaires en\nparallèle. Pour exécuter les tests unitaires, j'ai dû lancer un test à la fois avec la commande\n",[183,6022,6023],{},"RUST_TEST_THREADS=1 cargo test",[11,6025,6026],{},"Cependant, j'ai réussi à modifier les tests unitaires pour qu'ils puissent s'exécuter en parallèle sans supprimer les\nméthodes statiques.",[127,6028,1620],{"id":1619},[11,6030,6031],{},"Maintenant que j'ai écrit ce petit programme, je vais pouvoir créer mon propre pool de stockage. De votre côté,\nn'hésitez pas à me faire des retours sur le programme. Je suis ouvert à toutes les critiques. De plus, si vous utilisez\nBackupPC (en version 4 uniquement et avec un pool de stockage en version 4), vous pouvez tester mon programme et\nl'utiliser.",[6033,6034,6035],"style",{},"html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}",{"title":1645,"searchDepth":1646,"depth":1646,"links":6037},[6038,6039,6044,6053],{"id":1806,"depth":1646,"text":1807},{"id":1868,"depth":1646,"text":1869,"children":6040},[6041,6042,6043],{"id":1911,"depth":1651,"text":1912},{"id":1984,"depth":1651,"text":1985},{"id":2003,"depth":1651,"text":2004},{"id":2076,"depth":1646,"text":2077,"children":6045},[6046,6047,6048,6049,6050,6051,6052],{"id":2100,"depth":1651,"text":2101},{"id":4349,"depth":1651,"text":4350},{"id":4762,"depth":1651,"text":4763},{"id":4799,"depth":1651,"text":4800},{"id":5803,"depth":1651,"text":5804},{"id":5822,"depth":1651,"text":5823},{"id":6005,"depth":1651,"text":6006},{"id":1619,"depth":1646,"text":1620},"Programmation","programmation","Une partie de cet article a été publiée sur LinuxFR.\nAprès avoir reçu quelques retours, j'ai décidé de publier une version modifiée et améliorée de cet article sur mon blog.",{"type":8,"value":6058},[6059,6064,6066,6068],[11,6060,1785,6061,1791],{},[48,6062,1790],{"href":1788,"rel":6063},[346],[11,6065,1794],{},[11,6067,1797],{},[11,6069,6070],{},[120,6071],{"src":1802,"alt":1645,"className":6072},[125],{"planet":1766},{"title":107,"description":6056},"pr_backuppc_pool","posts\u002FWoodstock\u002F2024-03-14_pr_backuppc_pool",[1682,1772,1773,6078,6079],"javascript","nodejs","JVBo68qbMHY70AJ8I83XDPaOAeqZhhqQNzs8jDhZhvY",{"id":6082,"title":6083,"author":6,"body":6084,"category":6054,"categorySlug":6055,"date":96,"description":1645,"excerpt":13576,"extension":1763,"location":1764,"meta":13584,"navigation":1766,"path":92,"published":1766,"seo":13585,"slug":13586,"stem":13587,"tags":13588,"timeToRead":2604,"__hash__":13589},"posts\u002Fposts\u002FWoodstock\u002F2023-05-10_woodstock_rust.md","Woodstock Backup - Optimiser la consommation mémoire de Node.js avec Rust",{"type":8,"value":6085,"toc":13566},[6086,6090,6093,6096,6100,6103,7589,7592,7595,7663,7666,7669,7673,7680,7683,7686,7689,7789,7792,7852,7858,7862,7865,7875,9787,9790,9850,9853,9856,9859,9863,9866,9869,9872,9875,9882,9885,9888,9891,11460,11463,11808,11811,11849,11852,11859,11862,11870,11873,11881,13116,13122,13493,13496,13499,13538,13541,13544,13547,13550,13552,13555,13563],[127,6087,6089],{"id":6088},"introduction","Introduction",[11,6091,6092],{},"Node.js est un environnement d'exécution JavaScript côté serveur qui repose sur le moteur JavaScript V8 de Google. Il\nest utilisé pour développer des applications serveur en back-end d'une application web, des outils en ligne de commande\net des applications desktop. Cependant, la consommation de mémoire peut être un problème pour certaines applications\nNode.js, en particulier celles qui manipulent de grandes quantités de données ou des données volumineuses.",[11,6094,6095],{},"Dans cet article, nous allons voir comment optimiser la consommation de mémoire d'une application Node.js en le couplant\navec Rust. Rust est un langage de programmation système qui offre des performances similaires à celles du C++, tout en\noffrant une sécurité de mémoire à la compilation. Rust peut être utilisé pour écrire des bibliothèques C\u002FC++ natives\npour Node.js.",[127,6097,6099],{"id":6098},"problématique","Problématique",[11,6101,6102],{},"J'ai développé un logiciel de sauvegarde appelé Woodstock Backup, écrit en TypeScript. Lors du lancement des\nsauvegardes, il crée une représentation du système de fichier en mémoire et nécessite une grande quantité de mémoire.\nPour illustrer cela, nous avons reproduit notre cas avec le code suivant :",[2122,6104,6108],{"className":6105,"code":6106,"language":6107,"meta":1645,"style":1645},"language-js shiki shiki-themes one-dark-pro","const filesize = require(\"filesize.js\");\nconst fs = require(\"fs\");\n\u002F\u002F Utilisation des méthodes de sérialisation et de désérialisation du moteur V8\nconst { serialize, deserialize } = require(\"v8\");\n\n\u002F\u002F Méthode pour générer une chaîne de caractère contenant des caractères aléatoires\nfunction randomString(size) {\n  const buffer = Buffer.alloc(size);\n  for (let i = 0; i \u003C size; i++) {\n    buffer[i] = Math.floor(Math.random() * 256);\n  }\n  return buffer;\n}\n\n\u002F\u002F Méthode pour générer un nombre aléatoire de la taille d'un nombre de 53 bits\nfunction randomNumber() {\n  return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);\n}\n\n\u002F\u002F Création d'un objet de test en javascript contenant que des données aléatoires\nconst testObject = () => ({\n  path: randomString(100),\n  stats: {\n    ownerId: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    groupId: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    size: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    compressedSize: {\n      low: randomNumber(),\n      high: randomNumber(),\n      unsigned: true,\n    },\n    lastRead: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    lastModified: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    created: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    mode: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    dev: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    rdev: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    ino: { low: randomNumber(), high: randomNumber(), unsigned: true },\n    nlink: { low: randomNumber(), high: randomNumber(), unsigned: true },\n  },\n  chunks: [randomString(32), randomString(32), randomString(32)],\n  sha256: randomString(32),\n});\n\n\u002F\u002F Lancement du GC pour s'assurer que la mémoire utilisé ne contient que les objets de test\nglobal.gc();\n\u002F\u002F On recupère la mémoire utilisé avant le test\nconst memoryBefore = process.memoryUsage().heapUsed;\n\u002F\u002F On recupère le temps avant le test (pour mesurer le temps de traitement)\nconst time = Date.now();\n\n\u002F\u002F Création des objets. J'ai du lancer plusieurs fois le script pour trouver une valeur qui ne causé pas de crash de \n\u002F\u002F Node.JS pour cause de manque de mémoire\nconst nbObjects = 1_300_000;\nconst testArray = new Array(nbObjects);\nfor (let i = 0; i \u003C nbObjects; i++) {\n  testArray[i] = testObject();\n}\n\n\u002F\u002F Combien de temps à pris la création des objets\nconsole.log(\"Creation time: \", Date.now() - time);\n\u002F\u002F Lancement du GC pour s'assurer que nous n'avons pas d'autres reliquats\nglobal.gc();\n\u002F\u002F Récupération de la mémoire après le test\nconst memoryAfter = process.memoryUsage().heapUsed;\n\nconsole.log(\"Memory consumption: \", filesize.default(memoryAfter - memoryBefore));\nconsole.log(\"Memory consumption by objects: \", filesize.default((memoryAfter - memoryBefore) \u002F nbObjects));\n\n\u002F\u002F Dans la suite on va écrire un fichier contenant le contenu de la mémoire. Cela a été fait initiallement pour \n\u002F\u002F s'assurer que le GC ne supprime pas mes objets car non utilisés.\nconst time2 = Date.now();\n\n\u002F\u002F Remove test file if exist\ntry {\n  fs.unlinkSync(\"test\");\n} catch (e) {}\n\nconst stream = fs.createWriteStream(\"test\");\n\n\u002F\u002F C'est moche, mais c'est pour tester\nstream.on(\"close\", () => {\n  console.log(\"Write to file time: \", Date.now() - time2);\n  \u002F\u002F Size of file test on disk\n  const stats = fs.statSync(\"test\");\n  console.log(\"Size of file on disk: \", filesize.default(stats.size));\n  console.log(\n    \"Size of object in the file\",\n    filesize.default(stats.size \u002F nbObjects)\n  );\n});\n\nfor (const obj of testArray) {\n  stream.write(serialize(obj));\n}\nstream.end();\n","js",[183,6109,6110,6130,6148,6153,6181,6185,6190,6206,6229,6265,6306,6311,6320,6324,6328,6333,6342,6370,6374,6378,6383,6401,6418,6426,6464,6495,6526,6533,6545,6556,6567,6572,6603,6634,6665,6696,6727,6758,6789,6820,6825,6859,6874,6879,6883,6888,6900,6905,6929,6934,6953,6957,6963,6969,6984,7006,7038,7056,7061,7066,7072,7104,7110,7121,7127,7149,7154,7190,7229,7234,7240,7246,7264,7269,7275,7283,7301,7316,7321,7344,7349,7355,7378,7409,7415,7438,7471,7482,7490,7515,7521,7526,7531,7550,7572,7577],{"__ignoreMap":1645},[2129,6111,6112,6115,6118,6120,6123,6125,6128],{"class":2131,"line":2132},[2129,6113,6114],{"class":2135},"const",[2129,6116,6117],{"class":2139}," filesize",[2129,6119,2206],{"class":2205},[2129,6121,6122],{"class":2188}," require",[2129,6124,2217],{"class":2143},[2129,6126,6127],{"class":2220},"\"filesize.js\"",[2129,6129,2256],{"class":2143},[2129,6131,6132,6134,6137,6139,6141,6143,6146],{"class":2131,"line":1646},[2129,6133,6114],{"class":2135},[2129,6135,6136],{"class":2139}," fs",[2129,6138,2206],{"class":2205},[2129,6140,6122],{"class":2188},[2129,6142,2217],{"class":2143},[2129,6144,6145],{"class":2220},"\"fs\"",[2129,6147,2256],{"class":2143},[2129,6149,6150],{"class":2131,"line":1651},[2129,6151,6152],{"class":2398},"\u002F\u002F Utilisation des méthodes de sérialisation et de désérialisation du moteur V8\n",[2129,6154,6155,6157,6159,6162,6164,6167,6170,6172,6174,6176,6179],{"class":2131,"line":2182},[2129,6156,6114],{"class":2135},[2129,6158,5940],{"class":2143},[2129,6160,6161],{"class":2139},"serialize",[2129,6163,931],{"class":2143},[2129,6165,6166],{"class":2139},"deserialize",[2129,6168,6169],{"class":2143}," } ",[2129,6171,2511],{"class":2205},[2129,6173,6122],{"class":2188},[2129,6175,2217],{"class":2143},[2129,6177,6178],{"class":2220},"\"v8\"",[2129,6180,2256],{"class":2143},[2129,6182,6183],{"class":2131,"line":2195},[2129,6184,2179],{"emptyLinePlaceholder":1766},[2129,6186,6187],{"class":2131,"line":2233},[2129,6188,6189],{"class":2398},"\u002F\u002F Méthode pour générer une chaîne de caractère contenant des caractères aléatoires\n",[2129,6191,6192,6195,6198,6200,6204],{"class":2131,"line":2259},[2129,6193,6194],{"class":2135},"function",[2129,6196,6197],{"class":2188}," randomString",[2129,6199,2217],{"class":2143},[2129,6201,6203],{"class":6202},"s_ZVi","size",[2129,6205,3177],{"class":2143},[2129,6207,6208,6211,6213,6215,6218,6220,6223,6225,6227],{"class":2131,"line":2286},[2129,6209,6210],{"class":2135},"  const",[2129,6212,2293],{"class":2139},[2129,6214,2206],{"class":2205},[2129,6216,6217],{"class":2139}," Buffer",[2129,6219,178],{"class":2143},[2129,6221,6222],{"class":2188},"alloc",[2129,6224,2217],{"class":2143},[2129,6226,6203],{"class":2201},[2129,6228,2256],{"class":2143},[2129,6230,6231,6234,6236,6239,6242,6244,6246,6248,6251,6253,6256,6258,6260,6263],{"class":2131,"line":2307},[2129,6232,6233],{"class":2135},"  for",[2129,6235,4546],{"class":2143},[2129,6237,6238],{"class":2135},"let",[2129,6240,6241],{"class":2201}," i",[2129,6243,2206],{"class":2205},[2129,6245,2659],{"class":2422},[2129,6247,4477],{"class":2143},[2129,6249,6250],{"class":2201},"i",[2129,6252,4229],{"class":2205},[2129,6254,6255],{"class":2201}," size",[2129,6257,4477],{"class":2143},[2129,6259,6250],{"class":2201},[2129,6261,6262],{"class":2205},"++",[2129,6264,3177],{"class":2143},[2129,6266,6267,6270,6272,6274,6276,6278,6281,6283,6286,6288,6291,6293,6296,6298,6301,6304],{"class":2131,"line":2332},[2129,6268,6269],{"class":2201},"    buffer",[2129,6271,2419],{"class":2143},[2129,6273,6250],{"class":2201},[2129,6275,2426],{"class":2143},[2129,6277,2511],{"class":2205},[2129,6279,6280],{"class":2139}," Math",[2129,6282,178],{"class":2143},[2129,6284,6285],{"class":2188},"floor",[2129,6287,2217],{"class":2143},[2129,6289,6290],{"class":2139},"Math",[2129,6292,178],{"class":2143},[2129,6294,6295],{"class":2188},"random",[2129,6297,2856],{"class":2143},[2129,6299,6300],{"class":2205},"*",[2129,6302,6303],{"class":2422}," 256",[2129,6305,2256],{"class":2143},[2129,6307,6308],{"class":2131,"line":2350},[2129,6309,6310],{"class":2143},"  }\n",[2129,6312,6313,6316,6318],{"class":2131,"line":2564},[2129,6314,6315],{"class":2135},"  return",[2129,6317,2293],{"class":2201},[2129,6319,2155],{"class":2143},[2129,6321,6322],{"class":2131,"line":2570},[2129,6323,2353],{"class":2143},[2129,6325,6326],{"class":2131,"line":2576},[2129,6327,2179],{"emptyLinePlaceholder":1766},[2129,6329,6330],{"class":2131,"line":2582},[2129,6331,6332],{"class":2398},"\u002F\u002F Méthode pour générer un nombre aléatoire de la taille d'un nombre de 53 bits\n",[2129,6334,6335,6337,6340],{"class":2131,"line":2587},[2129,6336,6194],{"class":2135},[2129,6338,6339],{"class":2188}," randomNumber",[2129,6341,2192],{"class":2143},[2129,6343,6344,6346,6348,6350,6352,6354,6356,6358,6360,6362,6364,6367],{"class":2131,"line":2604},[2129,6345,6315],{"class":2135},[2129,6347,6280],{"class":2139},[2129,6349,178],{"class":2143},[2129,6351,6285],{"class":2188},[2129,6353,2217],{"class":2143},[2129,6355,6290],{"class":2139},[2129,6357,178],{"class":2143},[2129,6359,6295],{"class":2188},[2129,6361,2856],{"class":2143},[2129,6363,6300],{"class":2205},[2129,6365,6366],{"class":2139}," Number",[2129,6368,6369],{"class":2143},".MAX_SAFE_INTEGER);\n",[2129,6371,6372],{"class":2131,"line":2610},[2129,6373,2353],{"class":2143},[2129,6375,6376],{"class":2131,"line":2644},[2129,6377,2179],{"emptyLinePlaceholder":1766},[2129,6379,6380],{"class":2131,"line":2664},[2129,6381,6382],{"class":2398},"\u002F\u002F Création d'un objet de test en javascript contenant que des données aléatoires\n",[2129,6384,6385,6387,6390,6392,6395,6398],{"class":2131,"line":3216},[2129,6386,6114],{"class":2135},[2129,6388,6389],{"class":2188}," testObject",[2129,6391,2206],{"class":2205},[2129,6393,6394],{"class":2143}," () ",[2129,6396,6397],{"class":2135},"=>",[2129,6399,6400],{"class":2143}," ({\n",[2129,6402,6403,6406,6408,6411,6413,6416],{"class":2131,"line":1776},[2129,6404,6405],{"class":2201},"  path",[2129,6407,2741],{"class":2143},[2129,6409,6410],{"class":2188},"randomString",[2129,6412,2217],{"class":2143},[2129,6414,6415],{"class":2422},"100",[2129,6417,5463],{"class":2143},[2129,6419,6420,6423],{"class":2131,"line":3248},[2129,6421,6422],{"class":2201},"  stats",[2129,6424,6425],{"class":2143},": {\n",[2129,6427,6428,6431,6434,6437,6439,6442,6445,6448,6450,6452,6454,6457,6459,6461],{"class":2131,"line":3278},[2129,6429,6430],{"class":2201},"    ownerId",[2129,6432,6433],{"class":2143},": { ",[2129,6435,6436],{"class":2201},"low",[2129,6438,2741],{"class":2143},[2129,6440,6441],{"class":2188},"randomNumber",[2129,6443,6444],{"class":2143},"(), ",[2129,6446,6447],{"class":2201},"high",[2129,6449,2741],{"class":2143},[2129,6451,6441],{"class":2188},[2129,6453,6444],{"class":2143},[2129,6455,6456],{"class":2201},"unsigned",[2129,6458,2741],{"class":2143},[2129,6460,3135],{"class":2422},[2129,6462,6463],{"class":2143}," },\n",[2129,6465,6466,6469,6471,6473,6475,6477,6479,6481,6483,6485,6487,6489,6491,6493],{"class":2131,"line":3293},[2129,6467,6468],{"class":2201},"    groupId",[2129,6470,6433],{"class":2143},[2129,6472,6436],{"class":2201},[2129,6474,2741],{"class":2143},[2129,6476,6441],{"class":2188},[2129,6478,6444],{"class":2143},[2129,6480,6447],{"class":2201},[2129,6482,2741],{"class":2143},[2129,6484,6441],{"class":2188},[2129,6486,6444],{"class":2143},[2129,6488,6456],{"class":2201},[2129,6490,2741],{"class":2143},[2129,6492,3135],{"class":2422},[2129,6494,6463],{"class":2143},[2129,6496,6497,6500,6502,6504,6506,6508,6510,6512,6514,6516,6518,6520,6522,6524],{"class":2131,"line":3310},[2129,6498,6499],{"class":2201},"    size",[2129,6501,6433],{"class":2143},[2129,6503,6436],{"class":2201},[2129,6505,2741],{"class":2143},[2129,6507,6441],{"class":2188},[2129,6509,6444],{"class":2143},[2129,6511,6447],{"class":2201},[2129,6513,2741],{"class":2143},[2129,6515,6441],{"class":2188},[2129,6517,6444],{"class":2143},[2129,6519,6456],{"class":2201},[2129,6521,2741],{"class":2143},[2129,6523,3135],{"class":2422},[2129,6525,6463],{"class":2143},[2129,6527,6528,6531],{"class":2131,"line":3330},[2129,6529,6530],{"class":2201},"    compressedSize",[2129,6532,6425],{"class":2143},[2129,6534,6535,6538,6540,6542],{"class":2131,"line":3335},[2129,6536,6537],{"class":2201},"      low",[2129,6539,2741],{"class":2143},[2129,6541,6441],{"class":2188},[2129,6543,6544],{"class":2143},"(),\n",[2129,6546,6547,6550,6552,6554],{"class":2131,"line":3359},[2129,6548,6549],{"class":2201},"      high",[2129,6551,2741],{"class":2143},[2129,6553,6441],{"class":2188},[2129,6555,6544],{"class":2143},[2129,6557,6558,6561,6563,6565],{"class":2131,"line":3374},[2129,6559,6560],{"class":2201},"      unsigned",[2129,6562,2741],{"class":2143},[2129,6564,3135],{"class":2422},[2129,6566,3022],{"class":2143},[2129,6568,6569],{"class":2131,"line":3379},[2129,6570,6571],{"class":2143},"    },\n",[2129,6573,6574,6577,6579,6581,6583,6585,6587,6589,6591,6593,6595,6597,6599,6601],{"class":2131,"line":3415},[2129,6575,6576],{"class":2201},"    lastRead",[2129,6578,6433],{"class":2143},[2129,6580,6436],{"class":2201},[2129,6582,2741],{"class":2143},[2129,6584,6441],{"class":2188},[2129,6586,6444],{"class":2143},[2129,6588,6447],{"class":2201},[2129,6590,2741],{"class":2143},[2129,6592,6441],{"class":2188},[2129,6594,6444],{"class":2143},[2129,6596,6456],{"class":2201},[2129,6598,2741],{"class":2143},[2129,6600,3135],{"class":2422},[2129,6602,6463],{"class":2143},[2129,6604,6605,6608,6610,6612,6614,6616,6618,6620,6622,6624,6626,6628,6630,6632],{"class":2131,"line":3434},[2129,6606,6607],{"class":2201},"    lastModified",[2129,6609,6433],{"class":2143},[2129,6611,6436],{"class":2201},[2129,6613,2741],{"class":2143},[2129,6615,6441],{"class":2188},[2129,6617,6444],{"class":2143},[2129,6619,6447],{"class":2201},[2129,6621,2741],{"class":2143},[2129,6623,6441],{"class":2188},[2129,6625,6444],{"class":2143},[2129,6627,6456],{"class":2201},[2129,6629,2741],{"class":2143},[2129,6631,3135],{"class":2422},[2129,6633,6463],{"class":2143},[2129,6635,6636,6639,6641,6643,6645,6647,6649,6651,6653,6655,6657,6659,6661,6663],{"class":2131,"line":3459},[2129,6637,6638],{"class":2201},"    created",[2129,6640,6433],{"class":2143},[2129,6642,6436],{"class":2201},[2129,6644,2741],{"class":2143},[2129,6646,6441],{"class":2188},[2129,6648,6444],{"class":2143},[2129,6650,6447],{"class":2201},[2129,6652,2741],{"class":2143},[2129,6654,6441],{"class":2188},[2129,6656,6444],{"class":2143},[2129,6658,6456],{"class":2201},[2129,6660,2741],{"class":2143},[2129,6662,3135],{"class":2422},[2129,6664,6463],{"class":2143},[2129,6666,6667,6670,6672,6674,6676,6678,6680,6682,6684,6686,6688,6690,6692,6694],{"class":2131,"line":3465},[2129,6668,6669],{"class":2201},"    mode",[2129,6671,6433],{"class":2143},[2129,6673,6436],{"class":2201},[2129,6675,2741],{"class":2143},[2129,6677,6441],{"class":2188},[2129,6679,6444],{"class":2143},[2129,6681,6447],{"class":2201},[2129,6683,2741],{"class":2143},[2129,6685,6441],{"class":2188},[2129,6687,6444],{"class":2143},[2129,6689,6456],{"class":2201},[2129,6691,2741],{"class":2143},[2129,6693,3135],{"class":2422},[2129,6695,6463],{"class":2143},[2129,6697,6698,6701,6703,6705,6707,6709,6711,6713,6715,6717,6719,6721,6723,6725],{"class":2131,"line":3480},[2129,6699,6700],{"class":2201},"    dev",[2129,6702,6433],{"class":2143},[2129,6704,6436],{"class":2201},[2129,6706,2741],{"class":2143},[2129,6708,6441],{"class":2188},[2129,6710,6444],{"class":2143},[2129,6712,6447],{"class":2201},[2129,6714,2741],{"class":2143},[2129,6716,6441],{"class":2188},[2129,6718,6444],{"class":2143},[2129,6720,6456],{"class":2201},[2129,6722,2741],{"class":2143},[2129,6724,3135],{"class":2422},[2129,6726,6463],{"class":2143},[2129,6728,6729,6732,6734,6736,6738,6740,6742,6744,6746,6748,6750,6752,6754,6756],{"class":2131,"line":3486},[2129,6730,6731],{"class":2201},"    rdev",[2129,6733,6433],{"class":2143},[2129,6735,6436],{"class":2201},[2129,6737,2741],{"class":2143},[2129,6739,6441],{"class":2188},[2129,6741,6444],{"class":2143},[2129,6743,6447],{"class":2201},[2129,6745,2741],{"class":2143},[2129,6747,6441],{"class":2188},[2129,6749,6444],{"class":2143},[2129,6751,6456],{"class":2201},[2129,6753,2741],{"class":2143},[2129,6755,3135],{"class":2422},[2129,6757,6463],{"class":2143},[2129,6759,6760,6763,6765,6767,6769,6771,6773,6775,6777,6779,6781,6783,6785,6787],{"class":2131,"line":3492},[2129,6761,6762],{"class":2201},"    ino",[2129,6764,6433],{"class":2143},[2129,6766,6436],{"class":2201},[2129,6768,2741],{"class":2143},[2129,6770,6441],{"class":2188},[2129,6772,6444],{"class":2143},[2129,6774,6447],{"class":2201},[2129,6776,2741],{"class":2143},[2129,6778,6441],{"class":2188},[2129,6780,6444],{"class":2143},[2129,6782,6456],{"class":2201},[2129,6784,2741],{"class":2143},[2129,6786,3135],{"class":2422},[2129,6788,6463],{"class":2143},[2129,6790,6791,6794,6796,6798,6800,6802,6804,6806,6808,6810,6812,6814,6816,6818],{"class":2131,"line":3497},[2129,6792,6793],{"class":2201},"    nlink",[2129,6795,6433],{"class":2143},[2129,6797,6436],{"class":2201},[2129,6799,2741],{"class":2143},[2129,6801,6441],{"class":2188},[2129,6803,6444],{"class":2143},[2129,6805,6447],{"class":2201},[2129,6807,2741],{"class":2143},[2129,6809,6441],{"class":2188},[2129,6811,6444],{"class":2143},[2129,6813,6456],{"class":2201},[2129,6815,2741],{"class":2143},[2129,6817,3135],{"class":2422},[2129,6819,6463],{"class":2143},[2129,6821,6822],{"class":2131,"line":3516},[2129,6823,6824],{"class":2143},"  },\n",[2129,6826,6827,6830,6832,6834,6836,6839,6842,6844,6846,6848,6850,6852,6854,6856],{"class":2131,"line":3521},[2129,6828,6829],{"class":2201},"  chunks",[2129,6831,4472],{"class":2143},[2129,6833,6410],{"class":2188},[2129,6835,2217],{"class":2143},[2129,6837,6838],{"class":2422},"32",[2129,6840,6841],{"class":2143},"), ",[2129,6843,6410],{"class":2188},[2129,6845,2217],{"class":2143},[2129,6847,6838],{"class":2422},[2129,6849,6841],{"class":2143},[2129,6851,6410],{"class":2188},[2129,6853,2217],{"class":2143},[2129,6855,6838],{"class":2422},[2129,6857,6858],{"class":2143},")],\n",[2129,6860,6861,6864,6866,6868,6870,6872],{"class":2131,"line":3526},[2129,6862,6863],{"class":2201},"  sha256",[2129,6865,2741],{"class":2143},[2129,6867,6410],{"class":2188},[2129,6869,2217],{"class":2143},[2129,6871,6838],{"class":2422},[2129,6873,5463],{"class":2143},[2129,6875,6876],{"class":2131,"line":3549},[2129,6877,6878],{"class":2143},"});\n",[2129,6880,6881],{"class":2131,"line":3554},[2129,6882,2179],{"emptyLinePlaceholder":1766},[2129,6884,6885],{"class":2131,"line":3559},[2129,6886,6887],{"class":2398},"\u002F\u002F Lancement du GC pour s'assurer que la mémoire utilisé ne contient que les objets de test\n",[2129,6889,6890,6893,6895,6898],{"class":2131,"line":3584},[2129,6891,6892],{"class":2139},"global",[2129,6894,178],{"class":2143},[2129,6896,6897],{"class":2188},"gc",[2129,6899,2230],{"class":2143},[2129,6901,6902],{"class":2131,"line":3599},[2129,6903,6904],{"class":2398},"\u002F\u002F On recupère la mémoire utilisé avant le test\n",[2129,6906,6907,6909,6912,6914,6917,6919,6922,6924,6927],{"class":2131,"line":3612},[2129,6908,6114],{"class":2135},[2129,6910,6911],{"class":2139}," memoryBefore",[2129,6913,2206],{"class":2205},[2129,6915,6916],{"class":2139}," process",[2129,6918,178],{"class":2143},[2129,6920,6921],{"class":2188},"memoryUsage",[2129,6923,3541],{"class":2143},[2129,6925,6926],{"class":2201},"heapUsed",[2129,6928,2155],{"class":2143},[2129,6930,6931],{"class":2131,"line":3627},[2129,6932,6933],{"class":2398},"\u002F\u002F On recupère le temps avant le test (pour mesurer le temps de traitement)\n",[2129,6935,6936,6938,6941,6943,6946,6948,6951],{"class":2131,"line":3632},[2129,6937,6114],{"class":2135},[2129,6939,6940],{"class":2139}," time",[2129,6942,2206],{"class":2205},[2129,6944,6945],{"class":2139}," Date",[2129,6947,178],{"class":2143},[2129,6949,6950],{"class":2188},"now",[2129,6952,2230],{"class":2143},[2129,6954,6955],{"class":2131,"line":3637},[2129,6956,2179],{"emptyLinePlaceholder":1766},[2129,6958,6960],{"class":2131,"line":6959},52,[2129,6961,6962],{"class":2398},"\u002F\u002F Création des objets. J'ai du lancer plusieurs fois le script pour trouver une valeur qui ne causé pas de crash de \n",[2129,6964,6966],{"class":2131,"line":6965},53,[2129,6967,6968],{"class":2398},"\u002F\u002F Node.JS pour cause de manque de mémoire\n",[2129,6970,6972,6974,6977,6979,6982],{"class":2131,"line":6971},54,[2129,6973,6114],{"class":2135},[2129,6975,6976],{"class":2139}," nbObjects",[2129,6978,2206],{"class":2205},[2129,6980,6981],{"class":2422}," 1_300_000",[2129,6983,2155],{"class":2143},[2129,6985,6987,6989,6992,6994,6996,6999,7001,7004],{"class":2131,"line":6986},55,[2129,6988,6114],{"class":2135},[2129,6990,6991],{"class":2139}," testArray",[2129,6993,2206],{"class":2205},[2129,6995,3094],{"class":2135},[2129,6997,6998],{"class":2188}," Array",[2129,7000,2217],{"class":2143},[2129,7002,7003],{"class":2201},"nbObjects",[2129,7005,2256],{"class":2143},[2129,7007,7009,7012,7014,7016,7018,7020,7022,7024,7026,7028,7030,7032,7034,7036],{"class":2131,"line":7008},56,[2129,7010,7011],{"class":2135},"for",[2129,7013,4546],{"class":2143},[2129,7015,6238],{"class":2135},[2129,7017,6241],{"class":2201},[2129,7019,2206],{"class":2205},[2129,7021,2659],{"class":2422},[2129,7023,4477],{"class":2143},[2129,7025,6250],{"class":2201},[2129,7027,4229],{"class":2205},[2129,7029,6976],{"class":2201},[2129,7031,4477],{"class":2143},[2129,7033,6250],{"class":2201},[2129,7035,6262],{"class":2205},[2129,7037,3177],{"class":2143},[2129,7039,7041,7044,7046,7048,7050,7052,7054],{"class":2131,"line":7040},57,[2129,7042,7043],{"class":2201},"  testArray",[2129,7045,2419],{"class":2143},[2129,7047,6250],{"class":2201},[2129,7049,2426],{"class":2143},[2129,7051,2511],{"class":2205},[2129,7053,6389],{"class":2188},[2129,7055,2230],{"class":2143},[2129,7057,7059],{"class":2131,"line":7058},58,[2129,7060,2353],{"class":2143},[2129,7062,7064],{"class":2131,"line":7063},59,[2129,7065,2179],{"emptyLinePlaceholder":1766},[2129,7067,7069],{"class":2131,"line":7068},60,[2129,7070,7071],{"class":2398},"\u002F\u002F Combien de temps à pris la création des objets\n",[2129,7073,7075,7078,7080,7083,7085,7088,7090,7092,7094,7096,7098,7100,7102],{"class":2131,"line":7074},61,[2129,7076,7077],{"class":2139},"console",[2129,7079,178],{"class":2143},[2129,7081,7082],{"class":2188},"log",[2129,7084,2217],{"class":2143},[2129,7086,7087],{"class":2220},"\"Creation time: \"",[2129,7089,931],{"class":2143},[2129,7091,35],{"class":2139},[2129,7093,178],{"class":2143},[2129,7095,6950],{"class":2188},[2129,7097,2856],{"class":2143},[2129,7099,2627],{"class":2205},[2129,7101,6940],{"class":2201},[2129,7103,2256],{"class":2143},[2129,7105,7107],{"class":2131,"line":7106},62,[2129,7108,7109],{"class":2398},"\u002F\u002F Lancement du GC pour s'assurer que nous n'avons pas d'autres reliquats\n",[2129,7111,7113,7115,7117,7119],{"class":2131,"line":7112},63,[2129,7114,6892],{"class":2139},[2129,7116,178],{"class":2143},[2129,7118,6897],{"class":2188},[2129,7120,2230],{"class":2143},[2129,7122,7124],{"class":2131,"line":7123},64,[2129,7125,7126],{"class":2398},"\u002F\u002F Récupération de la mémoire après le test\n",[2129,7128,7130,7132,7135,7137,7139,7141,7143,7145,7147],{"class":2131,"line":7129},65,[2129,7131,6114],{"class":2135},[2129,7133,7134],{"class":2139}," memoryAfter",[2129,7136,2206],{"class":2205},[2129,7138,6916],{"class":2139},[2129,7140,178],{"class":2143},[2129,7142,6921],{"class":2188},[2129,7144,3541],{"class":2143},[2129,7146,6926],{"class":2201},[2129,7148,2155],{"class":2143},[2129,7150,7152],{"class":2131,"line":7151},66,[2129,7153,2179],{"emptyLinePlaceholder":1766},[2129,7155,7157,7159,7161,7163,7165,7168,7170,7173,7175,7178,7180,7183,7186,7188],{"class":2131,"line":7156},67,[2129,7158,7077],{"class":2139},[2129,7160,178],{"class":2143},[2129,7162,7082],{"class":2188},[2129,7164,2217],{"class":2143},[2129,7166,7167],{"class":2220},"\"Memory consumption: \"",[2129,7169,931],{"class":2143},[2129,7171,7172],{"class":2139},"filesize",[2129,7174,178],{"class":2143},[2129,7176,7177],{"class":2188},"default",[2129,7179,2217],{"class":2143},[2129,7181,7182],{"class":2201},"memoryAfter",[2129,7184,7185],{"class":2205}," -",[2129,7187,6911],{"class":2201},[2129,7189,4135],{"class":2143},[2129,7191,7193,7195,7197,7199,7201,7204,7206,7208,7210,7212,7215,7217,7219,7221,7223,7225,7227],{"class":2131,"line":7192},68,[2129,7194,7077],{"class":2139},[2129,7196,178],{"class":2143},[2129,7198,7082],{"class":2188},[2129,7200,2217],{"class":2143},[2129,7202,7203],{"class":2220},"\"Memory consumption by objects: \"",[2129,7205,931],{"class":2143},[2129,7207,7172],{"class":2139},[2129,7209,178],{"class":2143},[2129,7211,7177],{"class":2188},[2129,7213,7214],{"class":2143},"((",[2129,7216,7182],{"class":2201},[2129,7218,7185],{"class":2205},[2129,7220,6911],{"class":2201},[2129,7222,4026],{"class":2143},[2129,7224,4846],{"class":2205},[2129,7226,6976],{"class":2201},[2129,7228,4135],{"class":2143},[2129,7230,7232],{"class":2131,"line":7231},69,[2129,7233,2179],{"emptyLinePlaceholder":1766},[2129,7235,7237],{"class":2131,"line":7236},70,[2129,7238,7239],{"class":2398},"\u002F\u002F Dans la suite on va écrire un fichier contenant le contenu de la mémoire. Cela a été fait initiallement pour \n",[2129,7241,7243],{"class":2131,"line":7242},71,[2129,7244,7245],{"class":2398},"\u002F\u002F s'assurer que le GC ne supprime pas mes objets car non utilisés.\n",[2129,7247,7249,7251,7254,7256,7258,7260,7262],{"class":2131,"line":7248},72,[2129,7250,6114],{"class":2135},[2129,7252,7253],{"class":2139}," time2",[2129,7255,2206],{"class":2205},[2129,7257,6945],{"class":2139},[2129,7259,178],{"class":2143},[2129,7261,6950],{"class":2188},[2129,7263,2230],{"class":2143},[2129,7265,7267],{"class":2131,"line":7266},73,[2129,7268,2179],{"emptyLinePlaceholder":1766},[2129,7270,7272],{"class":2131,"line":7271},74,[2129,7273,7274],{"class":2398},"\u002F\u002F Remove test file if exist\n",[2129,7276,7278,7281],{"class":2131,"line":7277},75,[2129,7279,7280],{"class":2135},"try",[2129,7282,2905],{"class":2143},[2129,7284,7286,7289,7291,7294,7296,7299],{"class":2131,"line":7285},76,[2129,7287,7288],{"class":2139},"  fs",[2129,7290,178],{"class":2143},[2129,7292,7293],{"class":2188},"unlinkSync",[2129,7295,2217],{"class":2143},[2129,7297,7298],{"class":2220},"\"test\"",[2129,7300,2256],{"class":2143},[2129,7302,7304,7306,7309,7311,7313],{"class":2131,"line":7303},77,[2129,7305,2523],{"class":2143},[2129,7307,7308],{"class":2135},"catch",[2129,7310,4546],{"class":2143},[2129,7312,3918],{"class":2201},[2129,7314,7315],{"class":2143},") {}\n",[2129,7317,7319],{"class":2131,"line":7318},78,[2129,7320,2179],{"emptyLinePlaceholder":1766},[2129,7322,7324,7326,7329,7331,7333,7335,7338,7340,7342],{"class":2131,"line":7323},79,[2129,7325,6114],{"class":2135},[2129,7327,7328],{"class":2139}," stream",[2129,7330,2206],{"class":2205},[2129,7332,6136],{"class":2139},[2129,7334,178],{"class":2143},[2129,7336,7337],{"class":2188},"createWriteStream",[2129,7339,2217],{"class":2143},[2129,7341,7298],{"class":2220},[2129,7343,2256],{"class":2143},[2129,7345,7347],{"class":2131,"line":7346},80,[2129,7348,2179],{"emptyLinePlaceholder":1766},[2129,7350,7352],{"class":2131,"line":7351},81,[2129,7353,7354],{"class":2398},"\u002F\u002F C'est moche, mais c'est pour tester\n",[2129,7356,7358,7361,7363,7366,7368,7371,7374,7376],{"class":2131,"line":7357},82,[2129,7359,7360],{"class":2139},"stream",[2129,7362,178],{"class":2143},[2129,7364,7365],{"class":2188},"on",[2129,7367,2217],{"class":2143},[2129,7369,7370],{"class":2220},"\"close\"",[2129,7372,7373],{"class":2143},", () ",[2129,7375,6397],{"class":2135},[2129,7377,2905],{"class":2143},[2129,7379,7381,7384,7386,7388,7390,7393,7395,7397,7399,7401,7403,7405,7407],{"class":2131,"line":7380},83,[2129,7382,7383],{"class":2139},"  console",[2129,7385,178],{"class":2143},[2129,7387,7082],{"class":2188},[2129,7389,2217],{"class":2143},[2129,7391,7392],{"class":2220},"\"Write to file time: \"",[2129,7394,931],{"class":2143},[2129,7396,35],{"class":2139},[2129,7398,178],{"class":2143},[2129,7400,6950],{"class":2188},[2129,7402,2856],{"class":2143},[2129,7404,2627],{"class":2205},[2129,7406,7253],{"class":2201},[2129,7408,2256],{"class":2143},[2129,7410,7412],{"class":2131,"line":7411},84,[2129,7413,7414],{"class":2398},"  \u002F\u002F Size of file test on disk\n",[2129,7416,7418,7420,7423,7425,7427,7429,7432,7434,7436],{"class":2131,"line":7417},85,[2129,7419,6210],{"class":2135},[2129,7421,7422],{"class":2139}," stats",[2129,7424,2206],{"class":2205},[2129,7426,6136],{"class":2139},[2129,7428,178],{"class":2143},[2129,7430,7431],{"class":2188},"statSync",[2129,7433,2217],{"class":2143},[2129,7435,7298],{"class":2220},[2129,7437,2256],{"class":2143},[2129,7439,7441,7443,7445,7447,7449,7452,7454,7456,7458,7460,7462,7465,7467,7469],{"class":2131,"line":7440},86,[2129,7442,7383],{"class":2139},[2129,7444,178],{"class":2143},[2129,7446,7082],{"class":2188},[2129,7448,2217],{"class":2143},[2129,7450,7451],{"class":2220},"\"Size of file on disk: \"",[2129,7453,931],{"class":2143},[2129,7455,7172],{"class":2139},[2129,7457,178],{"class":2143},[2129,7459,7177],{"class":2188},[2129,7461,2217],{"class":2143},[2129,7463,7464],{"class":2139},"stats",[2129,7466,178],{"class":2143},[2129,7468,6203],{"class":2201},[2129,7470,4135],{"class":2143},[2129,7472,7474,7476,7478,7480],{"class":2131,"line":7473},87,[2129,7475,7383],{"class":2139},[2129,7477,178],{"class":2143},[2129,7479,7082],{"class":2188},[2129,7481,4631],{"class":2143},[2129,7483,7485,7488],{"class":2131,"line":7484},88,[2129,7486,7487],{"class":2220},"    \"Size of object in the file\"",[2129,7489,3022],{"class":2143},[2129,7491,7493,7496,7498,7500,7502,7504,7506,7508,7511,7513],{"class":2131,"line":7492},89,[2129,7494,7495],{"class":2139},"    filesize",[2129,7497,178],{"class":2143},[2129,7499,7177],{"class":2188},[2129,7501,2217],{"class":2143},[2129,7503,7464],{"class":2139},[2129,7505,178],{"class":2143},[2129,7507,6203],{"class":2201},[2129,7509,7510],{"class":2205}," \u002F",[2129,7512,6976],{"class":2201},[2129,7514,2975],{"class":2143},[2129,7516,7518],{"class":2131,"line":7517},90,[2129,7519,7520],{"class":2143},"  );\n",[2129,7522,7524],{"class":2131,"line":7523},91,[2129,7525,6878],{"class":2143},[2129,7527,7529],{"class":2131,"line":7528},92,[2129,7530,2179],{"emptyLinePlaceholder":1766},[2129,7532,7534,7536,7538,7540,7543,7546,7548],{"class":2131,"line":7533},93,[2129,7535,7011],{"class":2135},[2129,7537,4546],{"class":2143},[2129,7539,6114],{"class":2135},[2129,7541,7542],{"class":2139}," obj",[2129,7544,7545],{"class":2135}," of",[2129,7547,6991],{"class":2201},[2129,7549,3177],{"class":2143},[2129,7551,7553,7556,7558,7561,7563,7565,7567,7570],{"class":2131,"line":7552},94,[2129,7554,7555],{"class":2139},"  stream",[2129,7557,178],{"class":2143},[2129,7559,7560],{"class":2188},"write",[2129,7562,2217],{"class":2143},[2129,7564,6161],{"class":2188},[2129,7566,2217],{"class":2143},[2129,7568,7569],{"class":2201},"obj",[2129,7571,4135],{"class":2143},[2129,7573,7575],{"class":2131,"line":7574},95,[2129,7576,2353],{"class":2143},[2129,7578,7580,7582,7584,7587],{"class":2131,"line":7579},96,[2129,7581,7360],{"class":2139},[2129,7583,178],{"class":2143},[2129,7585,7586],{"class":2188},"end",[2129,7588,2230],{"class":2143},[11,7590,7591],{},"En estimant rapidement la mémoire que la taille de l'objet aurait dû prendre, je l'estime à environ 432 octets (12\nnombres de 2*64 bits + 1 octet de boolean + 128 caractères pour les chunks et 100 caractères pour le nom).",[11,7593,7594],{},"Le code n'est pas le plus propre, mais le but est ici d'illustrer rapidement la problématique. Voici le résultat de ce\ntest :",[21,7596,7597,7605],{},[24,7598,7599],{},[27,7600,7601,7603],{},[30,7602],{},[30,7604],{},[40,7606,7607,7615,7623,7631,7639,7647,7655],{},[27,7608,7609,7612],{},[45,7610,7611],{},"Nombre d'objets créés",[45,7613,7614],{},"1 300 000",[27,7616,7617,7620],{},[45,7618,7619],{},"Temps de création",[45,7621,7622],{},"15 secondes",[27,7624,7625,7628],{},[45,7626,7627],{},"Consommation mémoire",[45,7629,7630],{},"2,8 Go",[27,7632,7633,7636],{},[45,7634,7635],{},"Consommation moyenne par objet",[45,7637,7638],{},"2,2 Ko",[27,7640,7641,7644],{},[45,7642,7643],{},"Temps d'écriture dans le fichier",[45,7645,7646],{},"91 secondes",[27,7648,7649,7652],{},[45,7650,7651],{},"Taille du fichier sur le disque",[45,7653,7654],{},"1,1 Go",[27,7656,7657,7660],{},[45,7658,7659],{},"Taille moyenne d'un objet dans le fichier",[45,7661,7662],{},"903 octets",[11,7664,7665],{},"Avec ce premier exemple, on peut déjà constater que la consommation mémoire est très importante. En effet, on est à\n2,2Ko par objet au lieu des 432 estimés (soit 5 fois plus). La taille des objets dans le fichier est déjà un peu plus\nraisonnable.",[11,7667,7668],{},"La seule explication au fait que la consommation mémoire soit aussi importante est, je pense, liée au fait que dans V8,\nles structures sont toutes des objets avec des méthodes par défaut et une structure minimaliste (les Buffers également).",[127,7670,7672],{"id":7671},"test-avec-un-bigint","Test avec un BigInt",[11,7674,7675,7676,7679],{},"La question que l'on peut se poser est : pourquoi utiliser la structure ",[183,7677,7678],{},"{ low: Number, high: Number, unsigned: Bool }"," ?",[11,7681,7682],{},"J'utilise cette structure car pour la persistance, j'utilise la librairie protobuf.js qui ne supporte pas les BigInt,\nmais qui utilise à la place la librairie long.js qui utilise cette structure.",[11,7684,7685],{},"J'ai donc effectué un test en remplaçant chaque objet complexe par le nouveau type BigInt de Node.js. Voici le code :",[11,7687,7688],{},"Pour générer le nombre aléatoire, je me base sur un entier de 2*53 bits.",[2122,7690,7692],{"className":6105,"code":7691,"language":6107,"meta":1645,"style":1645},"function randomNumber() {\n  return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);\n}\n\nfunction randomBigInt() {\n  return (BigInt(randomNumber()) \u003C\u003C 53n) + BigInt(randomNumber());\n}\n",[183,7693,7694,7702,7728,7732,7736,7745,7785],{"__ignoreMap":1645},[2129,7695,7696,7698,7700],{"class":2131,"line":2132},[2129,7697,6194],{"class":2135},[2129,7699,6339],{"class":2188},[2129,7701,2192],{"class":2143},[2129,7703,7704,7706,7708,7710,7712,7714,7716,7718,7720,7722,7724,7726],{"class":2131,"line":1646},[2129,7705,6315],{"class":2135},[2129,7707,6280],{"class":2139},[2129,7709,178],{"class":2143},[2129,7711,6285],{"class":2188},[2129,7713,2217],{"class":2143},[2129,7715,6290],{"class":2139},[2129,7717,178],{"class":2143},[2129,7719,6295],{"class":2188},[2129,7721,2856],{"class":2143},[2129,7723,6300],{"class":2205},[2129,7725,6366],{"class":2139},[2129,7727,6369],{"class":2143},[2129,7729,7730],{"class":2131,"line":1651},[2129,7731,2353],{"class":2143},[2129,7733,7734],{"class":2131,"line":2182},[2129,7735,2179],{"emptyLinePlaceholder":1766},[2129,7737,7738,7740,7743],{"class":2131,"line":2195},[2129,7739,6194],{"class":2135},[2129,7741,7742],{"class":2188}," randomBigInt",[2129,7744,2192],{"class":2143},[2129,7746,7747,7749,7751,7754,7756,7758,7761,7764,7767,7770,7772,7775,7778,7780,7782],{"class":2131,"line":2233},[2129,7748,6315],{"class":2135},[2129,7750,4546],{"class":2143},[2129,7752,7753],{"class":2188},"BigInt",[2129,7755,2217],{"class":2143},[2129,7757,6441],{"class":2188},[2129,7759,7760],{"class":2143},"()) ",[2129,7762,7763],{"class":2205},"\u003C\u003C",[2129,7765,7766],{"class":2422}," 53",[2129,7768,7769],{"class":2135},"n",[2129,7771,4026],{"class":2143},[2129,7773,7774],{"class":2205},"+",[2129,7776,7777],{"class":2188}," BigInt",[2129,7779,2217],{"class":2143},[2129,7781,6441],{"class":2188},[2129,7783,7784],{"class":2143},"());\n",[2129,7786,7787],{"class":2131,"line":2259},[2129,7788,2353],{"class":2143},[11,7790,7791],{},"Le résultat est le suivant:",[21,7793,7794,7802],{},[24,7795,7796],{},[27,7797,7798,7800],{},[30,7799],{},[30,7801],{},[40,7803,7804,7810,7817,7824,7831,7838,7845],{},[27,7805,7806,7808],{},[45,7807,7611],{},[45,7809,7614],{},[27,7811,7812,7814],{},[45,7813,7619],{},[45,7815,7816],{},"14 secondes",[27,7818,7819,7821],{},[45,7820,7627],{},[45,7822,7823],{},"2,1 Go",[27,7825,7826,7828],{},[45,7827,7635],{},[45,7829,7830],{},"1,7 Ko",[27,7832,7833,7835],{},[45,7834,7643],{},[45,7836,7837],{},"40 secondes",[27,7839,7840,7842],{},[45,7841,7651],{},[45,7843,7844],{},"747,8 Mo",[27,7846,7847,7849],{},[45,7848,7659],{},[45,7850,7851],{},"603 octets",[11,7853,7854,7855,7857],{},"L'utilisation de ",[183,7856,7753],{}," permet de réduire la consommation mémoire de 25% et la taille des objets dans le fichier de\n33%. La taille des objets sur le disque est acceptable. En revanche, la consommation mémoire de Node.js reste trop\nimportante.",[127,7859,7861],{"id":7860},"ecriture-du-même-code-en-rust","Ecriture du même code en Rust",[11,7863,7864],{},"Il est possible d'optimiser certaines parties de l'application grâce à la notion de node_modules natifs. Habituellement,\nces modules sont écrits en C++ en utilisant N-API. Il existe des bindings pour Rust, ce qui permet d'écrire une partie\nde l'application en Rust et de l'utiliser dans Node.JS.",[11,7866,7867,7868,7871,7872,178],{},"Pour comparer les performances de Rust et de Node.js, j'ai écrit le même code en Rust. Pour les nombres, je me suis basé\nsur des entiers de 64 bits et pour le nom de fichier, sur un ",[183,7869,7870],{},"Vec\u003Cu8>",". Je me suis basé sur une table de 32 caractères\npour le ",[183,7873,7874],{},"Sha256",[2122,7876,7878],{"className":2124,"code":7877,"language":1774,"meta":1645,"style":1645},"use humansize::{format_size, make_format, DECIMAL};\nuse procinfo::pid;\nuse rand::{rngs::ThreadRng, Rng};\nuse serde::{Deserialize, Serialize};\nuse std::io::BufWriter;\n\n#[derive(Serialize, Deserialize, Debug)]\nstruct Stats {\n    owner_id: u64,\n    group_id: u64,\n    size: u64,\n    compressed_size: u64,\n    last_read: u64,\n    last_modified: u64,\n    created: u64,\n    mode: u64,\n    dev: u64,\n    rdev: u64,\n    ino: u64,\n    nlink: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug)]\nstruct Sha256 {\n    data: [u8; 32],\n}\n\n#[derive(Serialize, Deserialize, Debug)]\nstruct TestObject {\n    #[serde(with = \"serde_bytes\")]\n    path: Vec\u003Cu8>,\n    stats: Stats,\n\n    chunks: Vec\u003CSha256>,\n    sha256: Sha256,\n}\n\nfn generate_random_number(rng: &mut ThreadRng) -> u64 {\n    rng.gen()\n}\n\nfn generate_random_vect(rng: &mut ThreadRng, size: usize) -> Vec\u003Cu8> {\n    let mut vect: Vec\u003Cu8> = Vec::with_capacity(size);\n    for _ in 0..size {\n        vect.push(rng.gen());\n    }\n    vect\n}\n\nfn generate_random_sha256(rng: &mut ThreadRng) -> Sha256 {\n    let mut sha256 = Sha256 { data: [0; 32] };\n    for i in 0..32 {\n        sha256.data[i] = rng.gen();\n    }\n    sha256\n}\n\nfn main() {\n    let formatter = make_format(DECIMAL);\n    let mut rng = rand::thread_rng();\n    \u002F\u002F Get the memory consumption of the current process\n    let memory = pid::statm_self().unwrap();\n    \u002F\u002F Get the current time in ms\n    let now = std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .unwrap()\n        .as_millis();\n\n    \u002F\u002F Create a vector with 1_300_000 elements of type TestObject\n    let nb_object = 1_300_000;\n    let mut test_objects: Vec\u003CTestObject> = Vec::with_capacity(nb_object);\n    for _ in 0..nb_object {\n        test_objects.push(TestObject {\n            path: generate_random_vect(&mut rng, 100),\n            stats: Stats {\n                owner_id: generate_random_number(&mut rng),\n                group_id: generate_random_number(&mut rng),\n                size: generate_random_number(&mut rng),\n                compressed_size: generate_random_number(&mut rng),\n                last_read: generate_random_number(&mut rng),\n                last_modified: generate_random_number(&mut rng),\n                created: generate_random_number(&mut rng),\n                mode: generate_random_number(&mut rng),\n                dev: generate_random_number(&mut rng),\n                rdev: generate_random_number(&mut rng),\n                ino: generate_random_number(&mut rng),\n                nlink: generate_random_number(&mut rng),\n            },\n            chunks: vec![\n                generate_random_sha256(&mut rng),\n                generate_random_sha256(&mut rng),\n                generate_random_sha256(&mut rng),\n            ],\n            sha256: generate_random_sha256(&mut rng),\n        });\n    }\n\n    let now_after_creation = std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .unwrap()\n        .as_millis();\n    \u002F\u002F Calculate the creation time\n    println!(\"Creation time: {} ms\", now_after_creation - now);\n\n    \u002F\u002F Get the memory consumption of the current process\n    let memory_after_creation = pid::statm_self().unwrap();\n    \u002F\u002F Show memory consumption\n    println!(\n        \"Memory consumption after creation: {}\",\n        formatter((memory_after_creation.size - memory.size) * 4096)\n    );\n    \u002F\u002F Show average memory consumption per object\n    println!(\n        \"Average memory consumption per object: {}\",\n        formatter(((memory_after_creation.size - memory.size) * 4096) \u002F nb_object)\n    );\n\n    \u002F\u002F Remove the file test.bin if it exists\n    match std::fs::remove_file(\"test.bin\") {\n        Ok(_) => {}\n        Err(_) => {}\n    }\n\n    \u002F\u002F Serialize all the objects in a file\n    let file = std::fs::File::create(\"test.bin\").unwrap();\n    let mut buffile = BufWriter::new(file);\n    for test_object in test_objects.iter() {\n        bincode::serialize_into(&mut buffile, test_object).unwrap();\n    }\n\n    \u002F\u002F Show time to write\n    let now_after_serialization = std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .unwrap()\n        .as_millis();\n\n    println!(\n        \"Write to file time: {} ms\",\n        now_after_serialization - now_after_creation\n    );\n\n    \u002F\u002F Get the file size and the average object size in file\n    let metadata = std::fs::metadata(\"test.bin\").unwrap();\n    println!(\n        \"Size of file on disk: {}\",\n        format_size(metadata.len(), DECIMAL)\n    );\n    println!(\n        \"Size of object in the file: {}\",\n        format_size(metadata.len() \u002F nb_object as u64, DECIMAL)\n    );\n}\n",[183,7879,7880,7896,7906,7931,7950,7967,7971,7990,7999,8010,8021,8031,8042,8053,8064,8074,8084,8094,8104,8114,8124,8128,8132,8148,8157,8172,8176,8180,8196,8205,8217,8232,8244,8248,8263,8274,8278,8282,8307,8319,8323,8327,8362,8396,8416,8436,8440,8445,8449,8453,8476,8505,8521,8544,8548,8553,8557,8561,8569,8587,8606,8611,8634,8639,8666,8689,8697,8706,8710,8715,8728,8763,8779,8794,8816,8827,8845,8862,8879,8896,8913,8930,8947,8964,8981,8998,9015,9032,9037,9050,9063,9075,9087,9092,9110,9115,9119,9124,9150,9171,9180,9189,9195,9217,9222,9227,9249,9255,9262,9270,9295,9301,9307,9314,9322,9347,9352,9357,9363,9386,9399,9411,9416,9421,9427,9461,9487,9506,9534,9539,9544,9550,9576,9597,9606,9615,9620,9627,9635,9646,9651,9656,9662,9693,9700,9708,9728,9733,9740,9748,9777,9782],{"__ignoreMap":1645},[2129,7881,7882,7884,7887,7890,7893],{"class":2131,"line":2132},[2129,7883,2136],{"class":2135},[2129,7885,7886],{"class":2139}," humansize",[2129,7888,7889],{"class":2143},"::{format_size, make_format, ",[2129,7891,7892],{"class":2139},"DECIMAL",[2129,7894,7895],{"class":2143},"};\n",[2129,7897,7898,7900,7903],{"class":2131,"line":1646},[2129,7899,2136],{"class":2135},[2129,7901,7902],{"class":2139}," procinfo",[2129,7904,7905],{"class":2143},"::pid;\n",[2129,7907,7908,7910,7913,7916,7919,7921,7924,7926,7929],{"class":2131,"line":1651},[2129,7909,2136],{"class":2135},[2129,7911,7912],{"class":2139}," rand",[2129,7914,7915],{"class":2143},"::{",[2129,7917,7918],{"class":2139},"rngs",[2129,7920,2144],{"class":2143},[2129,7922,7923],{"class":2139},"ThreadRng",[2129,7925,931],{"class":2143},[2129,7927,7928],{"class":2139},"Rng",[2129,7930,7895],{"class":2143},[2129,7932,7933,7935,7938,7940,7943,7945,7948],{"class":2131,"line":2182},[2129,7934,2136],{"class":2135},[2129,7936,7937],{"class":2139}," serde",[2129,7939,7915],{"class":2143},[2129,7941,7942],{"class":2139},"Deserialize",[2129,7944,931],{"class":2143},[2129,7946,7947],{"class":2139},"Serialize",[2129,7949,7895],{"class":2143},[2129,7951,7952,7954,7956,7958,7960,7962,7965],{"class":2131,"line":2195},[2129,7953,2136],{"class":2135},[2129,7955,2162],{"class":2139},[2129,7957,2144],{"class":2143},[2129,7959,2798],{"class":2139},[2129,7961,2144],{"class":2143},[2129,7963,7964],{"class":2139},"BufWriter",[2129,7966,2155],{"class":2143},[2129,7968,7969],{"class":2131,"line":2233},[2129,7970,2179],{"emptyLinePlaceholder":1766},[2129,7972,7973,7976,7978,7980,7982,7984,7987],{"class":2131,"line":2259},[2129,7974,7975],{"class":2143},"#[derive(",[2129,7977,7947],{"class":2139},[2129,7979,931],{"class":2143},[2129,7981,7942],{"class":2139},[2129,7983,931],{"class":2143},[2129,7985,7986],{"class":2139},"Debug",[2129,7988,7989],{"class":2143},")]\n",[2129,7991,7992,7994,7997],{"class":2131,"line":2286},[2129,7993,2997],{"class":2135},[2129,7995,7996],{"class":2139}," Stats",[2129,7998,2905],{"class":2143},[2129,8000,8001,8004,8006,8008],{"class":2131,"line":2307},[2129,8002,8003],{"class":2201},"    owner_id",[2129,8005,2741],{"class":2143},[2129,8007,4418],{"class":2139},[2129,8009,3022],{"class":2143},[2129,8011,8012,8015,8017,8019],{"class":2131,"line":2332},[2129,8013,8014],{"class":2201},"    group_id",[2129,8016,2741],{"class":2143},[2129,8018,4418],{"class":2139},[2129,8020,3022],{"class":2143},[2129,8022,8023,8025,8027,8029],{"class":2131,"line":2350},[2129,8024,6499],{"class":2201},[2129,8026,2741],{"class":2143},[2129,8028,4418],{"class":2139},[2129,8030,3022],{"class":2143},[2129,8032,8033,8036,8038,8040],{"class":2131,"line":2564},[2129,8034,8035],{"class":2201},"    compressed_size",[2129,8037,2741],{"class":2143},[2129,8039,4418],{"class":2139},[2129,8041,3022],{"class":2143},[2129,8043,8044,8047,8049,8051],{"class":2131,"line":2570},[2129,8045,8046],{"class":2201},"    last_read",[2129,8048,2741],{"class":2143},[2129,8050,4418],{"class":2139},[2129,8052,3022],{"class":2143},[2129,8054,8055,8058,8060,8062],{"class":2131,"line":2576},[2129,8056,8057],{"class":2201},"    last_modified",[2129,8059,2741],{"class":2143},[2129,8061,4418],{"class":2139},[2129,8063,3022],{"class":2143},[2129,8065,8066,8068,8070,8072],{"class":2131,"line":2582},[2129,8067,6638],{"class":2201},[2129,8069,2741],{"class":2143},[2129,8071,4418],{"class":2139},[2129,8073,3022],{"class":2143},[2129,8075,8076,8078,8080,8082],{"class":2131,"line":2587},[2129,8077,6669],{"class":2201},[2129,8079,2741],{"class":2143},[2129,8081,4418],{"class":2139},[2129,8083,3022],{"class":2143},[2129,8085,8086,8088,8090,8092],{"class":2131,"line":2604},[2129,8087,6700],{"class":2201},[2129,8089,2741],{"class":2143},[2129,8091,4418],{"class":2139},[2129,8093,3022],{"class":2143},[2129,8095,8096,8098,8100,8102],{"class":2131,"line":2610},[2129,8097,6731],{"class":2201},[2129,8099,2741],{"class":2143},[2129,8101,4418],{"class":2139},[2129,8103,3022],{"class":2143},[2129,8105,8106,8108,8110,8112],{"class":2131,"line":2644},[2129,8107,6762],{"class":2201},[2129,8109,2741],{"class":2143},[2129,8111,4418],{"class":2139},[2129,8113,3022],{"class":2143},[2129,8115,8116,8118,8120,8122],{"class":2131,"line":2664},[2129,8117,6793],{"class":2201},[2129,8119,2741],{"class":2143},[2129,8121,4418],{"class":2139},[2129,8123,3022],{"class":2143},[2129,8125,8126],{"class":2131,"line":3216},[2129,8127,2353],{"class":2143},[2129,8129,8130],{"class":2131,"line":1776},[2129,8131,2179],{"emptyLinePlaceholder":1766},[2129,8133,8134,8136,8138,8140,8142,8144,8146],{"class":2131,"line":3248},[2129,8135,7975],{"class":2143},[2129,8137,7947],{"class":2139},[2129,8139,931],{"class":2143},[2129,8141,7942],{"class":2139},[2129,8143,931],{"class":2143},[2129,8145,7986],{"class":2139},[2129,8147,7989],{"class":2143},[2129,8149,8150,8152,8155],{"class":2131,"line":3278},[2129,8151,2997],{"class":2135},[2129,8153,8154],{"class":2139}," Sha256",[2129,8156,2905],{"class":2143},[2129,8158,8159,8162,8164,8166,8168,8170],{"class":2131,"line":3293},[2129,8160,8161],{"class":2201},"    data",[2129,8163,4472],{"class":2143},[2129,8165,2792],{"class":2139},[2129,8167,4477],{"class":2143},[2129,8169,6838],{"class":2422},[2129,8171,5494],{"class":2143},[2129,8173,8174],{"class":2131,"line":3310},[2129,8175,2353],{"class":2143},[2129,8177,8178],{"class":2131,"line":3330},[2129,8179,2179],{"emptyLinePlaceholder":1766},[2129,8181,8182,8184,8186,8188,8190,8192,8194],{"class":2131,"line":3335},[2129,8183,7975],{"class":2143},[2129,8185,7947],{"class":2139},[2129,8187,931],{"class":2143},[2129,8189,7942],{"class":2139},[2129,8191,931],{"class":2143},[2129,8193,7986],{"class":2139},[2129,8195,7989],{"class":2143},[2129,8197,8198,8200,8203],{"class":2131,"line":3359},[2129,8199,2997],{"class":2135},[2129,8201,8202],{"class":2139}," TestObject",[2129,8204,2905],{"class":2143},[2129,8206,8207,8210,8212,8215],{"class":2131,"line":3374},[2129,8208,8209],{"class":2143},"    #[serde(with ",[2129,8211,2511],{"class":2205},[2129,8213,8214],{"class":2220}," \"serde_bytes\"",[2129,8216,7989],{"class":2143},[2129,8218,8219,8221,8223,8225,8227,8229],{"class":2131,"line":3379},[2129,8220,5883],{"class":2201},[2129,8222,2741],{"class":2143},[2129,8224,3049],{"class":2139},[2129,8226,2735],{"class":2143},[2129,8228,2792],{"class":2139},[2129,8230,8231],{"class":2143},">,\n",[2129,8233,8234,8237,8239,8242],{"class":2131,"line":3415},[2129,8235,8236],{"class":2201},"    stats",[2129,8238,2741],{"class":2143},[2129,8240,8241],{"class":2139},"Stats",[2129,8243,3022],{"class":2143},[2129,8245,8246],{"class":2131,"line":3434},[2129,8247,2179],{"emptyLinePlaceholder":1766},[2129,8249,8250,8253,8255,8257,8259,8261],{"class":2131,"line":3459},[2129,8251,8252],{"class":2201},"    chunks",[2129,8254,2741],{"class":2143},[2129,8256,3049],{"class":2139},[2129,8258,2735],{"class":2143},[2129,8260,7874],{"class":2139},[2129,8262,8231],{"class":2143},[2129,8264,8265,8268,8270,8272],{"class":2131,"line":3465},[2129,8266,8267],{"class":2201},"    sha256",[2129,8269,2741],{"class":2143},[2129,8271,7874],{"class":2139},[2129,8273,3022],{"class":2143},[2129,8275,8276],{"class":2131,"line":3480},[2129,8277,2353],{"class":2143},[2129,8279,8280],{"class":2131,"line":3486},[2129,8281,2179],{"emptyLinePlaceholder":1766},[2129,8283,8284,8286,8289,8291,8294,8296,8298,8301,8303,8305],{"class":2131,"line":3492},[2129,8285,2185],{"class":2135},[2129,8287,8288],{"class":2188}," generate_random_number",[2129,8290,2217],{"class":2143},[2129,8292,8293],{"class":2201},"rng",[2129,8295,2784],{"class":2143},[2129,8297,2321],{"class":2135},[2129,8299,8300],{"class":2139}," ThreadRng",[2129,8302,3106],{"class":2143},[2129,8304,4418],{"class":2139},[2129,8306,2905],{"class":2143},[2129,8308,8309,8312,8314,8317],{"class":2131,"line":3497},[2129,8310,8311],{"class":2201},"    rng",[2129,8313,178],{"class":2143},[2129,8315,8316],{"class":2188},"gen",[2129,8318,5066],{"class":2143},[2129,8320,8321],{"class":2131,"line":3516},[2129,8322,2353],{"class":2143},[2129,8324,8325],{"class":2131,"line":3521},[2129,8326,2179],{"emptyLinePlaceholder":1766},[2129,8328,8329,8331,8334,8336,8338,8340,8342,8344,8346,8348,8350,8352,8354,8356,8358,8360],{"class":2131,"line":3526},[2129,8330,2185],{"class":2135},[2129,8332,8333],{"class":2188}," generate_random_vect",[2129,8335,2217],{"class":2143},[2129,8337,8293],{"class":2201},[2129,8339,2784],{"class":2143},[2129,8341,2321],{"class":2135},[2129,8343,8300],{"class":2139},[2129,8345,931],{"class":2143},[2129,8347,6203],{"class":2201},[2129,8349,2741],{"class":2143},[2129,8351,2808],{"class":2139},[2129,8353,3106],{"class":2143},[2129,8355,3049],{"class":2139},[2129,8357,2735],{"class":2143},[2129,8359,2792],{"class":2139},[2129,8361,2761],{"class":2143},[2129,8363,8364,8366,8368,8371,8373,8375,8377,8379,8381,8383,8385,8387,8390,8392,8394],{"class":2131,"line":3549},[2129,8365,2198],{"class":2135},[2129,8367,2264],{"class":2135},[2129,8369,8370],{"class":2201}," vect",[2129,8372,2741],{"class":2143},[2129,8374,3049],{"class":2139},[2129,8376,2735],{"class":2143},[2129,8378,2792],{"class":2139},[2129,8380,2747],{"class":2143},[2129,8382,2511],{"class":2205},[2129,8384,2298],{"class":2139},[2129,8386,2144],{"class":2143},[2129,8388,8389],{"class":2188},"with_capacity",[2129,8391,2217],{"class":2143},[2129,8393,6203],{"class":2201},[2129,8395,2256],{"class":2143},[2129,8397,8398,8401,8404,8407,8409,8412,8414],{"class":2131,"line":3554},[2129,8399,8400],{"class":2135},"    for",[2129,8402,8403],{"class":2201}," _",[2129,8405,8406],{"class":2135}," in",[2129,8408,2659],{"class":2422},[2129,8410,8411],{"class":2143},"..",[2129,8413,6203],{"class":2201},[2129,8415,2905],{"class":2143},[2129,8417,8418,8421,8423,8426,8428,8430,8432,8434],{"class":2131,"line":3559},[2129,8419,8420],{"class":2201},"        vect",[2129,8422,178],{"class":2143},[2129,8424,8425],{"class":2188},"push",[2129,8427,2217],{"class":2143},[2129,8429,8293],{"class":2201},[2129,8431,178],{"class":2143},[2129,8433,8316],{"class":2135},[2129,8435,7784],{"class":2143},[2129,8437,8438],{"class":2131,"line":3584},[2129,8439,2980],{"class":2143},[2129,8441,8442],{"class":2131,"line":3599},[2129,8443,8444],{"class":2201},"    vect\n",[2129,8446,8447],{"class":2131,"line":3612},[2129,8448,2353],{"class":2143},[2129,8450,8451],{"class":2131,"line":3627},[2129,8452,2179],{"emptyLinePlaceholder":1766},[2129,8454,8455,8457,8460,8462,8464,8466,8468,8470,8472,8474],{"class":2131,"line":3632},[2129,8456,2185],{"class":2135},[2129,8458,8459],{"class":2188}," generate_random_sha256",[2129,8461,2217],{"class":2143},[2129,8463,8293],{"class":2201},[2129,8465,2784],{"class":2143},[2129,8467,2321],{"class":2135},[2129,8469,8300],{"class":2139},[2129,8471,3106],{"class":2143},[2129,8473,7874],{"class":2139},[2129,8475,2905],{"class":2143},[2129,8477,8478,8480,8482,8485,8487,8489,8491,8494,8496,8498,8500,8502],{"class":2131,"line":3637},[2129,8479,2198],{"class":2135},[2129,8481,2264],{"class":2135},[2129,8483,8484],{"class":2201}," sha256",[2129,8486,2206],{"class":2205},[2129,8488,8154],{"class":2139},[2129,8490,5940],{"class":2143},[2129,8492,8493],{"class":2201},"data",[2129,8495,4472],{"class":2143},[2129,8497,2423],{"class":2422},[2129,8499,4477],{"class":2143},[2129,8501,6838],{"class":2422},[2129,8503,8504],{"class":2143},"] };\n",[2129,8506,8507,8509,8511,8513,8515,8517,8519],{"class":2131,"line":6959},[2129,8508,8400],{"class":2135},[2129,8510,6241],{"class":2201},[2129,8512,8406],{"class":2135},[2129,8514,2659],{"class":2422},[2129,8516,8411],{"class":2143},[2129,8518,6838],{"class":2422},[2129,8520,2905],{"class":2143},[2129,8522,8523,8526,8529,8531,8533,8535,8538,8540,8542],{"class":2131,"line":6965},[2129,8524,8525],{"class":2201},"        sha256",[2129,8527,8528],{"class":2143},".data[",[2129,8530,6250],{"class":2201},[2129,8532,2426],{"class":2143},[2129,8534,2511],{"class":2205},[2129,8536,8537],{"class":2201}," rng",[2129,8539,178],{"class":2143},[2129,8541,8316],{"class":2188},[2129,8543,2230],{"class":2143},[2129,8545,8546],{"class":2131,"line":6971},[2129,8547,2980],{"class":2143},[2129,8549,8550],{"class":2131,"line":6986},[2129,8551,8552],{"class":2201},"    sha256\n",[2129,8554,8555],{"class":2131,"line":7008},[2129,8556,2353],{"class":2143},[2129,8558,8559],{"class":2131,"line":7040},[2129,8560,2179],{"emptyLinePlaceholder":1766},[2129,8562,8563,8565,8567],{"class":2131,"line":7058},[2129,8564,2185],{"class":2135},[2129,8566,2189],{"class":2188},[2129,8568,2192],{"class":2143},[2129,8570,8571,8573,8576,8578,8581,8583,8585],{"class":2131,"line":7063},[2129,8572,2198],{"class":2135},[2129,8574,8575],{"class":2201}," formatter",[2129,8577,2206],{"class":2205},[2129,8579,8580],{"class":2188}," make_format",[2129,8582,2217],{"class":2143},[2129,8584,7892],{"class":2422},[2129,8586,2256],{"class":2143},[2129,8588,8589,8591,8593,8595,8597,8599,8601,8604],{"class":2131,"line":7068},[2129,8590,2198],{"class":2135},[2129,8592,2264],{"class":2135},[2129,8594,8537],{"class":2201},[2129,8596,2206],{"class":2205},[2129,8598,7912],{"class":2139},[2129,8600,2144],{"class":2143},[2129,8602,8603],{"class":2188},"thread_rng",[2129,8605,2230],{"class":2143},[2129,8607,8608],{"class":2131,"line":7074},[2129,8609,8610],{"class":2398},"    \u002F\u002F Get the memory consumption of the current process\n",[2129,8612,8613,8615,8618,8620,8623,8625,8628,8630,8632],{"class":2131,"line":7106},[2129,8614,2198],{"class":2135},[2129,8616,8617],{"class":2201}," memory",[2129,8619,2206],{"class":2205},[2129,8621,8622],{"class":2139}," pid",[2129,8624,2144],{"class":2143},[2129,8626,8627],{"class":2188},"statm_self",[2129,8629,3541],{"class":2143},[2129,8631,2227],{"class":2188},[2129,8633,2230],{"class":2143},[2129,8635,8636],{"class":2131,"line":7112},[2129,8637,8638],{"class":2398},"    \u002F\u002F Get the current time in ms\n",[2129,8640,8641,8643,8646,8648,8650,8652,8655,8657,8660,8662,8664],{"class":2131,"line":7123},[2129,8642,2198],{"class":2135},[2129,8644,8645],{"class":2201}," now",[2129,8647,2206],{"class":2205},[2129,8649,2162],{"class":2139},[2129,8651,2144],{"class":2143},[2129,8653,8654],{"class":2139},"time",[2129,8656,2144],{"class":2143},[2129,8658,8659],{"class":2139},"SystemTime",[2129,8661,2144],{"class":2143},[2129,8663,6950],{"class":2188},[2129,8665,5066],{"class":2143},[2129,8667,8668,8671,8674,8676,8678,8680,8682,8684,8687],{"class":2131,"line":7129},[2129,8669,8670],{"class":2143},"        .",[2129,8672,8673],{"class":2188},"duration_since",[2129,8675,2217],{"class":2143},[2129,8677,5154],{"class":2139},[2129,8679,2144],{"class":2143},[2129,8681,8654],{"class":2139},[2129,8683,2144],{"class":2143},[2129,8685,8686],{"class":2422},"UNIX_EPOCH",[2129,8688,2975],{"class":2143},[2129,8690,8691,8693,8695],{"class":2131,"line":7151},[2129,8692,8670],{"class":2143},[2129,8694,2227],{"class":2188},[2129,8696,5066],{"class":2143},[2129,8698,8699,8701,8704],{"class":2131,"line":7156},[2129,8700,8670],{"class":2143},[2129,8702,8703],{"class":2188},"as_millis",[2129,8705,2230],{"class":2143},[2129,8707,8708],{"class":2131,"line":7192},[2129,8709,2179],{"emptyLinePlaceholder":1766},[2129,8711,8712],{"class":2131,"line":7231},[2129,8713,8714],{"class":2398},"    \u002F\u002F Create a vector with 1_300_000 elements of type TestObject\n",[2129,8716,8717,8719,8722,8724,8726],{"class":2131,"line":7236},[2129,8718,2198],{"class":2135},[2129,8720,8721],{"class":2201}," nb_object",[2129,8723,2206],{"class":2205},[2129,8725,6981],{"class":2422},[2129,8727,2155],{"class":2143},[2129,8729,8730,8732,8734,8737,8739,8741,8743,8746,8748,8750,8752,8754,8756,8758,8761],{"class":2131,"line":7242},[2129,8731,2198],{"class":2135},[2129,8733,2264],{"class":2135},[2129,8735,8736],{"class":2201}," test_objects",[2129,8738,2741],{"class":2143},[2129,8740,3049],{"class":2139},[2129,8742,2735],{"class":2143},[2129,8744,8745],{"class":2139},"TestObject",[2129,8747,2747],{"class":2143},[2129,8749,2511],{"class":2205},[2129,8751,2298],{"class":2139},[2129,8753,2144],{"class":2143},[2129,8755,8389],{"class":2188},[2129,8757,2217],{"class":2143},[2129,8759,8760],{"class":2201},"nb_object",[2129,8762,2256],{"class":2143},[2129,8764,8765,8767,8769,8771,8773,8775,8777],{"class":2131,"line":7248},[2129,8766,8400],{"class":2135},[2129,8768,8403],{"class":2201},[2129,8770,8406],{"class":2135},[2129,8772,2659],{"class":2422},[2129,8774,8411],{"class":2143},[2129,8776,8760],{"class":2201},[2129,8778,2905],{"class":2143},[2129,8780,8781,8784,8786,8788,8790,8792],{"class":2131,"line":7266},[2129,8782,8783],{"class":2201},"        test_objects",[2129,8785,178],{"class":2143},[2129,8787,8425],{"class":2188},[2129,8789,2217],{"class":2143},[2129,8791,8745],{"class":2139},[2129,8793,2905],{"class":2143},[2129,8795,8796,8799,8801,8804,8806,8808,8810,8812,8814],{"class":2131,"line":7271},[2129,8797,8798],{"class":2201},"            path",[2129,8800,2741],{"class":2143},[2129,8802,8803],{"class":2188},"generate_random_vect",[2129,8805,2318],{"class":2143},[2129,8807,2321],{"class":2135},[2129,8809,8537],{"class":2201},[2129,8811,931],{"class":2143},[2129,8813,6415],{"class":2422},[2129,8815,5463],{"class":2143},[2129,8817,8818,8821,8823,8825],{"class":2131,"line":7277},[2129,8819,8820],{"class":2201},"            stats",[2129,8822,2741],{"class":2143},[2129,8824,8241],{"class":2139},[2129,8826,2905],{"class":2143},[2129,8828,8829,8832,8834,8837,8839,8841,8843],{"class":2131,"line":7285},[2129,8830,8831],{"class":2201},"                owner_id",[2129,8833,2741],{"class":2143},[2129,8835,8836],{"class":2188},"generate_random_number",[2129,8838,2318],{"class":2143},[2129,8840,2321],{"class":2135},[2129,8842,8537],{"class":2201},[2129,8844,5463],{"class":2143},[2129,8846,8847,8850,8852,8854,8856,8858,8860],{"class":2131,"line":7303},[2129,8848,8849],{"class":2201},"                group_id",[2129,8851,2741],{"class":2143},[2129,8853,8836],{"class":2188},[2129,8855,2318],{"class":2143},[2129,8857,2321],{"class":2135},[2129,8859,8537],{"class":2201},[2129,8861,5463],{"class":2143},[2129,8863,8864,8867,8869,8871,8873,8875,8877],{"class":2131,"line":7318},[2129,8865,8866],{"class":2201},"                size",[2129,8868,2741],{"class":2143},[2129,8870,8836],{"class":2188},[2129,8872,2318],{"class":2143},[2129,8874,2321],{"class":2135},[2129,8876,8537],{"class":2201},[2129,8878,5463],{"class":2143},[2129,8880,8881,8884,8886,8888,8890,8892,8894],{"class":2131,"line":7323},[2129,8882,8883],{"class":2201},"                compressed_size",[2129,8885,2741],{"class":2143},[2129,8887,8836],{"class":2188},[2129,8889,2318],{"class":2143},[2129,8891,2321],{"class":2135},[2129,8893,8537],{"class":2201},[2129,8895,5463],{"class":2143},[2129,8897,8898,8901,8903,8905,8907,8909,8911],{"class":2131,"line":7346},[2129,8899,8900],{"class":2201},"                last_read",[2129,8902,2741],{"class":2143},[2129,8904,8836],{"class":2188},[2129,8906,2318],{"class":2143},[2129,8908,2321],{"class":2135},[2129,8910,8537],{"class":2201},[2129,8912,5463],{"class":2143},[2129,8914,8915,8918,8920,8922,8924,8926,8928],{"class":2131,"line":7351},[2129,8916,8917],{"class":2201},"                last_modified",[2129,8919,2741],{"class":2143},[2129,8921,8836],{"class":2188},[2129,8923,2318],{"class":2143},[2129,8925,2321],{"class":2135},[2129,8927,8537],{"class":2201},[2129,8929,5463],{"class":2143},[2129,8931,8932,8935,8937,8939,8941,8943,8945],{"class":2131,"line":7357},[2129,8933,8934],{"class":2201},"                created",[2129,8936,2741],{"class":2143},[2129,8938,8836],{"class":2188},[2129,8940,2318],{"class":2143},[2129,8942,2321],{"class":2135},[2129,8944,8537],{"class":2201},[2129,8946,5463],{"class":2143},[2129,8948,8949,8952,8954,8956,8958,8960,8962],{"class":2131,"line":7380},[2129,8950,8951],{"class":2201},"                mode",[2129,8953,2741],{"class":2143},[2129,8955,8836],{"class":2188},[2129,8957,2318],{"class":2143},[2129,8959,2321],{"class":2135},[2129,8961,8537],{"class":2201},[2129,8963,5463],{"class":2143},[2129,8965,8966,8969,8971,8973,8975,8977,8979],{"class":2131,"line":7411},[2129,8967,8968],{"class":2201},"                dev",[2129,8970,2741],{"class":2143},[2129,8972,8836],{"class":2188},[2129,8974,2318],{"class":2143},[2129,8976,2321],{"class":2135},[2129,8978,8537],{"class":2201},[2129,8980,5463],{"class":2143},[2129,8982,8983,8986,8988,8990,8992,8994,8996],{"class":2131,"line":7417},[2129,8984,8985],{"class":2201},"                rdev",[2129,8987,2741],{"class":2143},[2129,8989,8836],{"class":2188},[2129,8991,2318],{"class":2143},[2129,8993,2321],{"class":2135},[2129,8995,8537],{"class":2201},[2129,8997,5463],{"class":2143},[2129,8999,9000,9003,9005,9007,9009,9011,9013],{"class":2131,"line":7440},[2129,9001,9002],{"class":2201},"                ino",[2129,9004,2741],{"class":2143},[2129,9006,8836],{"class":2188},[2129,9008,2318],{"class":2143},[2129,9010,2321],{"class":2135},[2129,9012,8537],{"class":2201},[2129,9014,5463],{"class":2143},[2129,9016,9017,9020,9022,9024,9026,9028,9030],{"class":2131,"line":7473},[2129,9018,9019],{"class":2201},"                nlink",[2129,9021,2741],{"class":2143},[2129,9023,8836],{"class":2188},[2129,9025,2318],{"class":2143},[2129,9027,2321],{"class":2135},[2129,9029,8537],{"class":2201},[2129,9031,5463],{"class":2143},[2129,9033,9034],{"class":2131,"line":7484},[2129,9035,9036],{"class":2143},"            },\n",[2129,9038,9039,9042,9044,9047],{"class":2131,"line":7492},[2129,9040,9041],{"class":2201},"            chunks",[2129,9043,2741],{"class":2143},[2129,9045,9046],{"class":2188},"vec!",[2129,9048,9049],{"class":2143},"[\n",[2129,9051,9052,9055,9057,9059,9061],{"class":2131,"line":7517},[2129,9053,9054],{"class":2188},"                generate_random_sha256",[2129,9056,2318],{"class":2143},[2129,9058,2321],{"class":2135},[2129,9060,8537],{"class":2201},[2129,9062,5463],{"class":2143},[2129,9064,9065,9067,9069,9071,9073],{"class":2131,"line":7523},[2129,9066,9054],{"class":2188},[2129,9068,2318],{"class":2143},[2129,9070,2321],{"class":2135},[2129,9072,8537],{"class":2201},[2129,9074,5463],{"class":2143},[2129,9076,9077,9079,9081,9083,9085],{"class":2131,"line":7528},[2129,9078,9054],{"class":2188},[2129,9080,2318],{"class":2143},[2129,9082,2321],{"class":2135},[2129,9084,8537],{"class":2201},[2129,9086,5463],{"class":2143},[2129,9088,9089],{"class":2131,"line":7533},[2129,9090,9091],{"class":2143},"            ],\n",[2129,9093,9094,9097,9099,9102,9104,9106,9108],{"class":2131,"line":7552},[2129,9095,9096],{"class":2201},"            sha256",[2129,9098,2741],{"class":2143},[2129,9100,9101],{"class":2188},"generate_random_sha256",[2129,9103,2318],{"class":2143},[2129,9105,2321],{"class":2135},[2129,9107,8537],{"class":2201},[2129,9109,5463],{"class":2143},[2129,9111,9112],{"class":2131,"line":7574},[2129,9113,9114],{"class":2143},"        });\n",[2129,9116,9117],{"class":2131,"line":7579},[2129,9118,2980],{"class":2143},[2129,9120,9122],{"class":2131,"line":9121},97,[2129,9123,2179],{"emptyLinePlaceholder":1766},[2129,9125,9127,9129,9132,9134,9136,9138,9140,9142,9144,9146,9148],{"class":2131,"line":9126},98,[2129,9128,2198],{"class":2135},[2129,9130,9131],{"class":2201}," now_after_creation",[2129,9133,2206],{"class":2205},[2129,9135,2162],{"class":2139},[2129,9137,2144],{"class":2143},[2129,9139,8654],{"class":2139},[2129,9141,2144],{"class":2143},[2129,9143,8659],{"class":2139},[2129,9145,2144],{"class":2143},[2129,9147,6950],{"class":2188},[2129,9149,5066],{"class":2143},[2129,9151,9153,9155,9157,9159,9161,9163,9165,9167,9169],{"class":2131,"line":9152},99,[2129,9154,8670],{"class":2143},[2129,9156,8673],{"class":2188},[2129,9158,2217],{"class":2143},[2129,9160,5154],{"class":2139},[2129,9162,2144],{"class":2143},[2129,9164,8654],{"class":2139},[2129,9166,2144],{"class":2143},[2129,9168,8686],{"class":2422},[2129,9170,2975],{"class":2143},[2129,9172,9174,9176,9178],{"class":2131,"line":9173},100,[2129,9175,8670],{"class":2143},[2129,9177,2227],{"class":2188},[2129,9179,5066],{"class":2143},[2129,9181,9183,9185,9187],{"class":2131,"line":9182},101,[2129,9184,8670],{"class":2143},[2129,9186,8703],{"class":2188},[2129,9188,2230],{"class":2143},[2129,9190,9192],{"class":2131,"line":9191},102,[2129,9193,9194],{"class":2398},"    \u002F\u002F Calculate the creation time\n",[2129,9196,9198,9200,9202,9205,9207,9210,9213,9215],{"class":2131,"line":9197},103,[2129,9199,2335],{"class":2188},[2129,9201,2217],{"class":2143},[2129,9203,9204],{"class":2220},"\"Creation time: {} ms\"",[2129,9206,931],{"class":2143},[2129,9208,9209],{"class":2201},"now_after_creation",[2129,9211,9212],{"class":2143}," - ",[2129,9214,6950],{"class":2201},[2129,9216,2256],{"class":2143},[2129,9218,9220],{"class":2131,"line":9219},104,[2129,9221,2179],{"emptyLinePlaceholder":1766},[2129,9223,9225],{"class":2131,"line":9224},105,[2129,9226,8610],{"class":2398},[2129,9228,9230,9232,9235,9237,9239,9241,9243,9245,9247],{"class":2131,"line":9229},106,[2129,9231,2198],{"class":2135},[2129,9233,9234],{"class":2201}," memory_after_creation",[2129,9236,2206],{"class":2205},[2129,9238,8622],{"class":2139},[2129,9240,2144],{"class":2143},[2129,9242,8627],{"class":2188},[2129,9244,3541],{"class":2143},[2129,9246,2227],{"class":2188},[2129,9248,2230],{"class":2143},[2129,9250,9252],{"class":2131,"line":9251},107,[2129,9253,9254],{"class":2398},"    \u002F\u002F Show memory consumption\n",[2129,9256,9258,9260],{"class":2131,"line":9257},108,[2129,9259,2335],{"class":2188},[2129,9261,4631],{"class":2143},[2129,9263,9265,9268],{"class":2131,"line":9264},109,[2129,9266,9267],{"class":2220},"        \"Memory consumption after creation: {}\"",[2129,9269,3022],{"class":2143},[2129,9271,9273,9276,9278,9281,9284,9287,9290,9293],{"class":2131,"line":9272},110,[2129,9274,9275],{"class":2188},"        formatter",[2129,9277,7214],{"class":2143},[2129,9279,9280],{"class":2201},"memory_after_creation",[2129,9282,9283],{"class":2143},".size - ",[2129,9285,9286],{"class":2201},"memory",[2129,9288,9289],{"class":2143},".size) * ",[2129,9291,9292],{"class":2422},"4096",[2129,9294,2975],{"class":2143},[2129,9296,9298],{"class":2131,"line":9297},111,[2129,9299,9300],{"class":2143},"    );\n",[2129,9302,9304],{"class":2131,"line":9303},112,[2129,9305,9306],{"class":2398},"    \u002F\u002F Show average memory consumption per object\n",[2129,9308,9310,9312],{"class":2131,"line":9309},113,[2129,9311,2335],{"class":2188},[2129,9313,4631],{"class":2143},[2129,9315,9317,9320],{"class":2131,"line":9316},114,[2129,9318,9319],{"class":2220},"        \"Average memory consumption per object: {}\"",[2129,9321,3022],{"class":2143},[2129,9323,9325,9327,9330,9332,9334,9336,9338,9340,9343,9345],{"class":2131,"line":9324},115,[2129,9326,9275],{"class":2188},[2129,9328,9329],{"class":2143},"(((",[2129,9331,9280],{"class":2201},[2129,9333,9283],{"class":2143},[2129,9335,9286],{"class":2201},[2129,9337,9289],{"class":2143},[2129,9339,9292],{"class":2422},[2129,9341,9342],{"class":2143},") \u002F ",[2129,9344,8760],{"class":2201},[2129,9346,2975],{"class":2143},[2129,9348,9350],{"class":2131,"line":9349},116,[2129,9351,9300],{"class":2143},[2129,9353,9355],{"class":2131,"line":9354},117,[2129,9356,2179],{"emptyLinePlaceholder":1766},[2129,9358,9360],{"class":2131,"line":9359},118,[2129,9361,9362],{"class":2398},"    \u002F\u002F Remove the file test.bin if it exists\n",[2129,9364,9366,9368,9370,9372,9374,9376,9379,9381,9384],{"class":2131,"line":9365},119,[2129,9367,4902],{"class":2135},[2129,9369,2162],{"class":2139},[2129,9371,2144],{"class":2143},[2129,9373,2167],{"class":2139},[2129,9375,2144],{"class":2143},[2129,9377,9378],{"class":2188},"remove_file",[2129,9380,2217],{"class":2143},[2129,9382,9383],{"class":2220},"\"test.bin\"",[2129,9385,3177],{"class":2143},[2129,9387,9389,9391,9393,9396],{"class":2131,"line":9388},120,[2129,9390,2968],{"class":2139},[2129,9392,2217],{"class":2143},[2129,9394,9395],{"class":2201},"_",[2129,9397,9398],{"class":2143},") => {}\n",[2129,9400,9402,9405,9407,9409],{"class":2131,"line":9401},121,[2129,9403,9404],{"class":2139},"        Err",[2129,9406,2217],{"class":2143},[2129,9408,9395],{"class":2201},[2129,9410,9398],{"class":2143},[2129,9412,9414],{"class":2131,"line":9413},122,[2129,9415,2980],{"class":2143},[2129,9417,9419],{"class":2131,"line":9418},123,[2129,9420,2179],{"emptyLinePlaceholder":1766},[2129,9422,9424],{"class":2131,"line":9423},124,[2129,9425,9426],{"class":2398},"    \u002F\u002F Serialize all the objects in a file\n",[2129,9428,9430,9432,9434,9436,9438,9440,9442,9444,9446,9448,9451,9453,9455,9457,9459],{"class":2131,"line":9429},125,[2129,9431,2198],{"class":2135},[2129,9433,5702],{"class":2201},[2129,9435,2206],{"class":2205},[2129,9437,2162],{"class":2139},[2129,9439,2144],{"class":2143},[2129,9441,2167],{"class":2139},[2129,9443,2144],{"class":2143},[2129,9445,2172],{"class":2139},[2129,9447,2144],{"class":2143},[2129,9449,9450],{"class":2188},"create",[2129,9452,2217],{"class":2143},[2129,9454,9383],{"class":2220},[2129,9456,2224],{"class":2143},[2129,9458,2227],{"class":2188},[2129,9460,2230],{"class":2143},[2129,9462,9464,9466,9468,9471,9473,9476,9478,9480,9482,9485],{"class":2131,"line":9463},126,[2129,9465,2198],{"class":2135},[2129,9467,2264],{"class":2135},[2129,9469,9470],{"class":2201}," buffile",[2129,9472,2206],{"class":2205},[2129,9474,9475],{"class":2139}," BufWriter",[2129,9477,2144],{"class":2143},[2129,9479,2248],{"class":2188},[2129,9481,2217],{"class":2143},[2129,9483,9484],{"class":2201},"file",[2129,9486,2256],{"class":2143},[2129,9488,9490,9492,9495,9497,9499,9501,9504],{"class":2131,"line":9489},127,[2129,9491,8400],{"class":2135},[2129,9493,9494],{"class":2201}," test_object",[2129,9496,8406],{"class":2135},[2129,9498,8736],{"class":2201},[2129,9500,178],{"class":2143},[2129,9502,9503],{"class":2188},"iter",[2129,9505,2192],{"class":2143},[2129,9507,9509,9512,9514,9517,9519,9521,9523,9525,9528,9530,9532],{"class":2131,"line":9508},128,[2129,9510,9511],{"class":2139},"        bincode",[2129,9513,2144],{"class":2143},[2129,9515,9516],{"class":2188},"serialize_into",[2129,9518,2318],{"class":2143},[2129,9520,2321],{"class":2135},[2129,9522,9470],{"class":2201},[2129,9524,931],{"class":2143},[2129,9526,9527],{"class":2201},"test_object",[2129,9529,2224],{"class":2143},[2129,9531,2227],{"class":2188},[2129,9533,2230],{"class":2143},[2129,9535,9537],{"class":2131,"line":9536},129,[2129,9538,2980],{"class":2143},[2129,9540,9542],{"class":2131,"line":9541},130,[2129,9543,2179],{"emptyLinePlaceholder":1766},[2129,9545,9547],{"class":2131,"line":9546},131,[2129,9548,9549],{"class":2398},"    \u002F\u002F Show time to write\n",[2129,9551,9553,9555,9558,9560,9562,9564,9566,9568,9570,9572,9574],{"class":2131,"line":9552},132,[2129,9554,2198],{"class":2135},[2129,9556,9557],{"class":2201}," now_after_serialization",[2129,9559,2206],{"class":2205},[2129,9561,2162],{"class":2139},[2129,9563,2144],{"class":2143},[2129,9565,8654],{"class":2139},[2129,9567,2144],{"class":2143},[2129,9569,8659],{"class":2139},[2129,9571,2144],{"class":2143},[2129,9573,6950],{"class":2188},[2129,9575,5066],{"class":2143},[2129,9577,9579,9581,9583,9585,9587,9589,9591,9593,9595],{"class":2131,"line":9578},133,[2129,9580,8670],{"class":2143},[2129,9582,8673],{"class":2188},[2129,9584,2217],{"class":2143},[2129,9586,5154],{"class":2139},[2129,9588,2144],{"class":2143},[2129,9590,8654],{"class":2139},[2129,9592,2144],{"class":2143},[2129,9594,8686],{"class":2422},[2129,9596,2975],{"class":2143},[2129,9598,9600,9602,9604],{"class":2131,"line":9599},134,[2129,9601,8670],{"class":2143},[2129,9603,2227],{"class":2188},[2129,9605,5066],{"class":2143},[2129,9607,9609,9611,9613],{"class":2131,"line":9608},135,[2129,9610,8670],{"class":2143},[2129,9612,8703],{"class":2188},[2129,9614,2230],{"class":2143},[2129,9616,9618],{"class":2131,"line":9617},136,[2129,9619,2179],{"emptyLinePlaceholder":1766},[2129,9621,9623,9625],{"class":2131,"line":9622},137,[2129,9624,2335],{"class":2188},[2129,9626,4631],{"class":2143},[2129,9628,9630,9633],{"class":2131,"line":9629},138,[2129,9631,9632],{"class":2220},"        \"Write to file time: {} ms\"",[2129,9634,3022],{"class":2143},[2129,9636,9638,9641,9643],{"class":2131,"line":9637},139,[2129,9639,9640],{"class":2201},"        now_after_serialization",[2129,9642,9212],{"class":2143},[2129,9644,9645],{"class":2201},"now_after_creation\n",[2129,9647,9649],{"class":2131,"line":9648},140,[2129,9650,9300],{"class":2143},[2129,9652,9654],{"class":2131,"line":9653},141,[2129,9655,2179],{"emptyLinePlaceholder":1766},[2129,9657,9659],{"class":2131,"line":9658},142,[2129,9660,9661],{"class":2398},"    \u002F\u002F Get the file size and the average object size in file\n",[2129,9663,9665,9667,9670,9672,9674,9676,9678,9680,9683,9685,9687,9689,9691],{"class":2131,"line":9664},143,[2129,9666,2198],{"class":2135},[2129,9668,9669],{"class":2201}," metadata",[2129,9671,2206],{"class":2205},[2129,9673,2162],{"class":2139},[2129,9675,2144],{"class":2143},[2129,9677,2167],{"class":2139},[2129,9679,2144],{"class":2143},[2129,9681,9682],{"class":2188},"metadata",[2129,9684,2217],{"class":2143},[2129,9686,9383],{"class":2220},[2129,9688,2224],{"class":2143},[2129,9690,2227],{"class":2188},[2129,9692,2230],{"class":2143},[2129,9694,9696,9698],{"class":2131,"line":9695},144,[2129,9697,2335],{"class":2188},[2129,9699,4631],{"class":2143},[2129,9701,9703,9706],{"class":2131,"line":9702},145,[2129,9704,9705],{"class":2220},"        \"Size of file on disk: {}\"",[2129,9707,3022],{"class":2143},[2129,9709,9711,9714,9716,9718,9720,9722,9724,9726],{"class":2131,"line":9710},146,[2129,9712,9713],{"class":2188},"        format_size",[2129,9715,2217],{"class":2143},[2129,9717,9682],{"class":2201},[2129,9719,178],{"class":2143},[2129,9721,2853],{"class":2188},[2129,9723,6444],{"class":2143},[2129,9725,7892],{"class":2422},[2129,9727,2975],{"class":2143},[2129,9729,9731],{"class":2131,"line":9730},147,[2129,9732,9300],{"class":2143},[2129,9734,9736,9738],{"class":2131,"line":9735},148,[2129,9737,2335],{"class":2188},[2129,9739,4631],{"class":2143},[2129,9741,9743,9746],{"class":2131,"line":9742},149,[2129,9744,9745],{"class":2220},"        \"Size of object in the file: {}\"",[2129,9747,3022],{"class":2143},[2129,9749,9751,9753,9755,9757,9759,9761,9764,9766,9769,9771,9773,9775],{"class":2131,"line":9750},150,[2129,9752,9713],{"class":2188},[2129,9754,2217],{"class":2143},[2129,9756,9682],{"class":2201},[2129,9758,178],{"class":2143},[2129,9760,2853],{"class":2188},[2129,9762,9763],{"class":2143},"() \u002F ",[2129,9765,8760],{"class":2201},[2129,9767,9768],{"class":2135}," as",[2129,9770,4563],{"class":2139},[2129,9772,931],{"class":2143},[2129,9774,7892],{"class":2422},[2129,9776,2975],{"class":2143},[2129,9778,9780],{"class":2131,"line":9779},151,[2129,9781,9300],{"class":2143},[2129,9783,9785],{"class":2131,"line":9784},152,[2129,9786,2353],{"class":2143},[11,9788,9789],{},"Ce qui donne le résultat suivant :",[21,9791,9792,9800],{},[24,9793,9794],{},[27,9795,9796,9798],{},[30,9797],{},[30,9799],{},[40,9801,9802,9808,9815,9822,9829,9836,9843],{},[27,9803,9804,9806],{},[45,9805,7611],{},[45,9807,7614],{},[27,9809,9810,9812],{},[45,9811,7619],{},[45,9813,9814],{},"2 secondes",[27,9816,9817,9819],{},[45,9818,7627],{},[45,9820,9821],{},"519,92 Mo",[27,9823,9824,9826],{},[45,9825,7635],{},[45,9827,9828],{},"399 octets",[27,9830,9831,9833],{},[45,9832,7643],{},[45,9834,9835],{},"1 seconde",[27,9837,9838,9840],{},[45,9839,7651],{},[45,9841,9842],{},"441,99 Mb",[27,9844,9845,9847],{},[45,9846,7659],{},[45,9848,9849],{},"339 octets",[11,9851,9852],{},"En comparant les résultats obtenus avec Node.js et Rust, on peut conclure que Rust est, sans contestation possible,\nbeaucoup plus performant que Node.js en termes de temps d'exécution et de consommation de mémoire pour la création de\n1,3 million d'objets. En effet, Rust a créé tous les objets en seulement 2 secondes et consommé 519,92 Mo de mémoire,\ntandis que Node.js a pris 15 secondes et consommé 2,8 Go de mémoire.",[11,9854,9855],{},"La consommation moyenne de mémoire par objet est du coup nettement plus faible avec Rust (399 octets) qu'avec\nNode.js (2,2 Ko).",[11,9857,9858],{},"En somme, cette première partie montre clairement que Rust est une alternative intéressante pour les applications\nnécessitant une manipulation intensive de données, en particulier lorsque la performance et la consommation de mémoire\nsont des critères importants.",[127,9860,9862],{"id":9861},"optimisation-de-notre-application","Optimisation de notre application",[11,9864,9865],{},"Pour optimiser notre application en TypeScript, nous allons développer un module natif en Node.js avec Rust. Pour cela,\nnous allons utiliser NAPI.RS.",[11,9867,9868],{},"N-API est une interface de programmation d'applications (API) qui permet aux modules natifs d'être facilement utilisés\ndans Node.js. Cela permet aux développeurs de créer des modules en C++ et de les utiliser dans des projets Node.js\nsans avoir à se soucier de la compatibilité entre les versions de Node.js.",[11,9870,9871],{},"N-API est une API stable et évolutive qui est maintenue par l'équipe Node.js. N-API fournit une interface de\nprogrammation d'applications indépendante du moteur JavaScript utilisé par Node.js. Cela signifie que les modules\ncompilés avec N-API fonctionnent de manière cohérente, quel que soit le moteur JavaScript utilisé par Node.js.",[11,9873,9874],{},"napi.rs est une bibliothèque Rust qui fournit une API pour écrire des modules Node.js en Rust. Elle simplifie la création\nde modules Node.js en fournissant des abstractions de niveau supérieur pour les fonctionnalités N-API. Avec napi.rs, les\ndéveloppeurs Rust peuvent facilement écrire des modules Node.js sans avoir à se soucier des détails techniques de N-API.",[11,9876,9877,9878,178],{},"Nous allons débuter le développement de la partie Rust de notre application. Je ne vais pas détailler la création d'un\nmodule Rust avec NAPI.RS. La documentation est assez bien faite pour cette partie : ",[48,9879,9880],{"href":9880,"rel":9881},"https:\u002F\u002Fnapi.rs\u002Fdocs\u002Fintroduction\u002Fsimple-package",[346],[11,9883,9884],{},"Le module que nous allons écrire doit conserver les objets qu'il crée en mémoire. En effet, si les objets créés côté Rust\nétaient ensuite transférés dans la partie Node.js, la consommation de mémoire serait la même que si nous avions créé\nles objets directement en Node.js.",[11,9886,9887],{},"L'exemple ci-dessous ne fait pas grand-chose, mais c'est un point à prendre en considération lorsque je vais développer\nle module pour mon application. Les objets transférés à la partie JavaScript ne doivent que transiter.",[11,9889,9890],{},"Voici la partie Rust :",[2122,9892,9894],{"className":2124,"code":9893,"language":1774,"meta":1645,"style":1645},"#![deny(clippy::all)]\nuse humansize::{format_size, DECIMAL};\nuse procinfo::pid;\nuse rand::{rngs::ThreadRng, Rng};\n\n#[macro_use]\nextern crate napi_derive;\n\nstruct Stats {\n  owner_id: u64,\n  group_id: u64,\n  size: u64,\n  compressed_size: u64,\n  last_read: u64,\n  last_modified: u64,\n  created: u64,\n  mode: u64,\n  dev: u64,\n  rdev: u64,\n  ino: u64,\n  nlink: u64,\n}\n\nstruct Sha256 {\n  data: [u8; 32],\n}\n\nstruct TestObject {\n  path: Vec\u003Cu8>,\n  stats: Stats,\n\n  chunks: Vec\u003CSha256>,\n  sha256: Sha256,\n}\n\nfn generate_random_number(rng: &mut ThreadRng) -> u64 {\n  rng.gen()\n}\n\nfn generate_random_vect(rng: &mut ThreadRng, size: usize) -> Vec\u003Cu8> {\n  let mut vect: Vec\u003Cu8> = Vec::with_capacity(size);\n  for _ in 0..size {\n    vect.push(rng.gen());\n  }\n  vect\n}\n\nfn generate_random_sha256(rng: &mut ThreadRng) -> Sha256 {\n  let mut sha256 = Sha256 { data: [0; 32] };\n  for i in 0..32 {\n    sha256.data[i] = rng.gen();\n  }\n  sha256\n}\n\npub struct TestObjectWrapper {\n  test_objects: Vec\u003CTestObject>,\n}\n\n#[napi(js_name = \"TestObjectWrapper\")]\npub struct JsTestObjectWrapper {\n  test_object_wrapper: TestObjectWrapper,\n}\n\n#[napi]\nimpl JsTestObjectWrapper {\n  #[napi(constructor)]\n  pub fn new() -> Self {\n    Self {\n      test_object_wrapper: TestObjectWrapper {\n        test_objects: Vec::new(),\n      },\n    }\n  }\n\n  #[napi]\n  pub fn fill(&mut self, nb_object: i32) {\n    let mut rng = rand::thread_rng();\n    \u002F\u002F Get the memory consumption of the current process\n    let memory = pid::statm_self().unwrap();\n    \u002F\u002F Get the current time in ms\n    let now = std::time::SystemTime::now()\n      .duration_since(std::time::UNIX_EPOCH)\n      .unwrap()\n      .as_millis();\n\n    self.test_object_wrapper.test_objects = Vec::with_capacity(nb_object.try_into().unwrap());\n\n    for _ in 0..nb_object {\n      self.test_object_wrapper.test_objects.push(TestObject {\n        path: generate_random_vect(&mut rng, 100),\n        stats: Stats {\n          owner_id: generate_random_number(&mut rng),\n          group_id: generate_random_number(&mut rng),\n          size: generate_random_number(&mut rng),\n          compressed_size: generate_random_number(&mut rng),\n          last_read: generate_random_number(&mut rng),\n          last_modified: generate_random_number(&mut rng),\n          created: generate_random_number(&mut rng),\n          mode: generate_random_number(&mut rng),\n          dev: generate_random_number(&mut rng),\n          rdev: generate_random_number(&mut rng),\n          ino: generate_random_number(&mut rng),\n          nlink: generate_random_number(&mut rng),\n        },\n        chunks: vec![\n          generate_random_sha256(&mut rng),\n          generate_random_sha256(&mut rng),\n          generate_random_sha256(&mut rng),\n        ],\n        sha256: generate_random_sha256(&mut rng),\n      });\n    }\n\n    let now_after_creation = std::time::SystemTime::now()\n      .duration_since(std::time::UNIX_EPOCH)\n      .unwrap()\n      .as_millis();\n    \u002F\u002F Calculate the creation time\n    println!(\"Creation time: {} ms\", now_after_creation - now);\n\n    \u002F\u002F Get the memory consumption of the current process\n    let memory_after_creation = pid::statm_self().unwrap();\n    \u002F\u002F Show memory consumption\n    println!(\n      \"Memory consumption after creation: {}\",\n      format_size((memory_after_creation.size - memory.size) * 4096, DECIMAL)\n    );\n    \u002F\u002F Show average memory consumption per object\n    println!(\n      \"Average memory consumption per object: {}\",\n      format_size(\n        ((memory_after_creation.size - memory.size) * 4096) \u002F nb_object as usize,\n        DECIMAL\n      )\n    );\n  }\n\n  #[napi(js_name = \"toString\")]\n  pub fn to_string(&self) -> String {\n    format!(\n      \"TestObjectWrapper {{ test_objects: {} }}\",\n      self.test_object_wrapper.test_objects.len()\n    )\n  }\n}\n",[183,9895,9896,9901,9914,9922,9942,9946,9951,9962,9966,9974,9985,9996,10007,10018,10029,10040,10051,10062,10073,10084,10095,10106,10110,10114,10122,10137,10141,10145,10153,10167,10177,10181,10195,10205,10209,10213,10235,10246,10250,10254,10288,10321,10337,10356,10360,10365,10369,10373,10395,10421,10437,10457,10461,10466,10470,10474,10485,10500,10504,10508,10520,10531,10543,10547,10551,10556,10564,10569,10585,10592,10603,10617,10622,10626,10630,10634,10639,10665,10683,10687,10707,10711,10735,10756,10764,10772,10776,10807,10811,10827,10843,10864,10875,10892,10909,10926,10943,10960,10977,10994,11011,11028,11045,11062,11079,11084,11095,11108,11120,11132,11137,11153,11158,11162,11166,11190,11210,11218,11226,11230,11248,11252,11256,11276,11280,11286,11293,11316,11320,11324,11330,11337,11343,11369,11374,11379,11383,11387,11391,11403,11423,11430,11437,11447,11452,11456],{"__ignoreMap":1645},[2129,9897,9898],{"class":2131,"line":2132},[2129,9899,9900],{"class":2143},"#![deny(clippy::all)]\n",[2129,9902,9903,9905,9907,9910,9912],{"class":2131,"line":1646},[2129,9904,2136],{"class":2135},[2129,9906,7886],{"class":2139},[2129,9908,9909],{"class":2143},"::{format_size, ",[2129,9911,7892],{"class":2139},[2129,9913,7895],{"class":2143},[2129,9915,9916,9918,9920],{"class":2131,"line":1651},[2129,9917,2136],{"class":2135},[2129,9919,7902],{"class":2139},[2129,9921,7905],{"class":2143},[2129,9923,9924,9926,9928,9930,9932,9934,9936,9938,9940],{"class":2131,"line":2182},[2129,9925,2136],{"class":2135},[2129,9927,7912],{"class":2139},[2129,9929,7915],{"class":2143},[2129,9931,7918],{"class":2139},[2129,9933,2144],{"class":2143},[2129,9935,7923],{"class":2139},[2129,9937,931],{"class":2143},[2129,9939,7928],{"class":2139},[2129,9941,7895],{"class":2143},[2129,9943,9944],{"class":2131,"line":2195},[2129,9945,2179],{"emptyLinePlaceholder":1766},[2129,9947,9948],{"class":2131,"line":2233},[2129,9949,9950],{"class":2143},"#[macro_use]\n",[2129,9952,9953,9956,9959],{"class":2131,"line":2259},[2129,9954,9955],{"class":2135},"extern",[2129,9957,9958],{"class":2135}," crate",[2129,9960,9961],{"class":2143}," napi_derive;\n",[2129,9963,9964],{"class":2131,"line":2286},[2129,9965,2179],{"emptyLinePlaceholder":1766},[2129,9967,9968,9970,9972],{"class":2131,"line":2307},[2129,9969,2997],{"class":2135},[2129,9971,7996],{"class":2139},[2129,9973,2905],{"class":2143},[2129,9975,9976,9979,9981,9983],{"class":2131,"line":2332},[2129,9977,9978],{"class":2201},"  owner_id",[2129,9980,2741],{"class":2143},[2129,9982,4418],{"class":2139},[2129,9984,3022],{"class":2143},[2129,9986,9987,9990,9992,9994],{"class":2131,"line":2350},[2129,9988,9989],{"class":2201},"  group_id",[2129,9991,2741],{"class":2143},[2129,9993,4418],{"class":2139},[2129,9995,3022],{"class":2143},[2129,9997,9998,10001,10003,10005],{"class":2131,"line":2564},[2129,9999,10000],{"class":2201},"  size",[2129,10002,2741],{"class":2143},[2129,10004,4418],{"class":2139},[2129,10006,3022],{"class":2143},[2129,10008,10009,10012,10014,10016],{"class":2131,"line":2570},[2129,10010,10011],{"class":2201},"  compressed_size",[2129,10013,2741],{"class":2143},[2129,10015,4418],{"class":2139},[2129,10017,3022],{"class":2143},[2129,10019,10020,10023,10025,10027],{"class":2131,"line":2576},[2129,10021,10022],{"class":2201},"  last_read",[2129,10024,2741],{"class":2143},[2129,10026,4418],{"class":2139},[2129,10028,3022],{"class":2143},[2129,10030,10031,10034,10036,10038],{"class":2131,"line":2582},[2129,10032,10033],{"class":2201},"  last_modified",[2129,10035,2741],{"class":2143},[2129,10037,4418],{"class":2139},[2129,10039,3022],{"class":2143},[2129,10041,10042,10045,10047,10049],{"class":2131,"line":2587},[2129,10043,10044],{"class":2201},"  created",[2129,10046,2741],{"class":2143},[2129,10048,4418],{"class":2139},[2129,10050,3022],{"class":2143},[2129,10052,10053,10056,10058,10060],{"class":2131,"line":2604},[2129,10054,10055],{"class":2201},"  mode",[2129,10057,2741],{"class":2143},[2129,10059,4418],{"class":2139},[2129,10061,3022],{"class":2143},[2129,10063,10064,10067,10069,10071],{"class":2131,"line":2610},[2129,10065,10066],{"class":2201},"  dev",[2129,10068,2741],{"class":2143},[2129,10070,4418],{"class":2139},[2129,10072,3022],{"class":2143},[2129,10074,10075,10078,10080,10082],{"class":2131,"line":2644},[2129,10076,10077],{"class":2201},"  rdev",[2129,10079,2741],{"class":2143},[2129,10081,4418],{"class":2139},[2129,10083,3022],{"class":2143},[2129,10085,10086,10089,10091,10093],{"class":2131,"line":2664},[2129,10087,10088],{"class":2201},"  ino",[2129,10090,2741],{"class":2143},[2129,10092,4418],{"class":2139},[2129,10094,3022],{"class":2143},[2129,10096,10097,10100,10102,10104],{"class":2131,"line":3216},[2129,10098,10099],{"class":2201},"  nlink",[2129,10101,2741],{"class":2143},[2129,10103,4418],{"class":2139},[2129,10105,3022],{"class":2143},[2129,10107,10108],{"class":2131,"line":1776},[2129,10109,2353],{"class":2143},[2129,10111,10112],{"class":2131,"line":3248},[2129,10113,2179],{"emptyLinePlaceholder":1766},[2129,10115,10116,10118,10120],{"class":2131,"line":3278},[2129,10117,2997],{"class":2135},[2129,10119,8154],{"class":2139},[2129,10121,2905],{"class":2143},[2129,10123,10124,10127,10129,10131,10133,10135],{"class":2131,"line":3293},[2129,10125,10126],{"class":2201},"  data",[2129,10128,4472],{"class":2143},[2129,10130,2792],{"class":2139},[2129,10132,4477],{"class":2143},[2129,10134,6838],{"class":2422},[2129,10136,5494],{"class":2143},[2129,10138,10139],{"class":2131,"line":3310},[2129,10140,2353],{"class":2143},[2129,10142,10143],{"class":2131,"line":3330},[2129,10144,2179],{"emptyLinePlaceholder":1766},[2129,10146,10147,10149,10151],{"class":2131,"line":3335},[2129,10148,2997],{"class":2135},[2129,10150,8202],{"class":2139},[2129,10152,2905],{"class":2143},[2129,10154,10155,10157,10159,10161,10163,10165],{"class":2131,"line":3359},[2129,10156,6405],{"class":2201},[2129,10158,2741],{"class":2143},[2129,10160,3049],{"class":2139},[2129,10162,2735],{"class":2143},[2129,10164,2792],{"class":2139},[2129,10166,8231],{"class":2143},[2129,10168,10169,10171,10173,10175],{"class":2131,"line":3374},[2129,10170,6422],{"class":2201},[2129,10172,2741],{"class":2143},[2129,10174,8241],{"class":2139},[2129,10176,3022],{"class":2143},[2129,10178,10179],{"class":2131,"line":3379},[2129,10180,2179],{"emptyLinePlaceholder":1766},[2129,10182,10183,10185,10187,10189,10191,10193],{"class":2131,"line":3415},[2129,10184,6829],{"class":2201},[2129,10186,2741],{"class":2143},[2129,10188,3049],{"class":2139},[2129,10190,2735],{"class":2143},[2129,10192,7874],{"class":2139},[2129,10194,8231],{"class":2143},[2129,10196,10197,10199,10201,10203],{"class":2131,"line":3434},[2129,10198,6863],{"class":2201},[2129,10200,2741],{"class":2143},[2129,10202,7874],{"class":2139},[2129,10204,3022],{"class":2143},[2129,10206,10207],{"class":2131,"line":3459},[2129,10208,2353],{"class":2143},[2129,10210,10211],{"class":2131,"line":3465},[2129,10212,2179],{"emptyLinePlaceholder":1766},[2129,10214,10215,10217,10219,10221,10223,10225,10227,10229,10231,10233],{"class":2131,"line":3480},[2129,10216,2185],{"class":2135},[2129,10218,8288],{"class":2188},[2129,10220,2217],{"class":2143},[2129,10222,8293],{"class":2201},[2129,10224,2784],{"class":2143},[2129,10226,2321],{"class":2135},[2129,10228,8300],{"class":2139},[2129,10230,3106],{"class":2143},[2129,10232,4418],{"class":2139},[2129,10234,2905],{"class":2143},[2129,10236,10237,10240,10242,10244],{"class":2131,"line":3486},[2129,10238,10239],{"class":2201},"  rng",[2129,10241,178],{"class":2143},[2129,10243,8316],{"class":2188},[2129,10245,5066],{"class":2143},[2129,10247,10248],{"class":2131,"line":3492},[2129,10249,2353],{"class":2143},[2129,10251,10252],{"class":2131,"line":3497},[2129,10253,2179],{"emptyLinePlaceholder":1766},[2129,10255,10256,10258,10260,10262,10264,10266,10268,10270,10272,10274,10276,10278,10280,10282,10284,10286],{"class":2131,"line":3516},[2129,10257,2185],{"class":2135},[2129,10259,8333],{"class":2188},[2129,10261,2217],{"class":2143},[2129,10263,8293],{"class":2201},[2129,10265,2784],{"class":2143},[2129,10267,2321],{"class":2135},[2129,10269,8300],{"class":2139},[2129,10271,931],{"class":2143},[2129,10273,6203],{"class":2201},[2129,10275,2741],{"class":2143},[2129,10277,2808],{"class":2139},[2129,10279,3106],{"class":2143},[2129,10281,3049],{"class":2139},[2129,10283,2735],{"class":2143},[2129,10285,2792],{"class":2139},[2129,10287,2761],{"class":2143},[2129,10289,10290,10293,10295,10297,10299,10301,10303,10305,10307,10309,10311,10313,10315,10317,10319],{"class":2131,"line":3521},[2129,10291,10292],{"class":2135},"  let",[2129,10294,2264],{"class":2135},[2129,10296,8370],{"class":2201},[2129,10298,2741],{"class":2143},[2129,10300,3049],{"class":2139},[2129,10302,2735],{"class":2143},[2129,10304,2792],{"class":2139},[2129,10306,2747],{"class":2143},[2129,10308,2511],{"class":2205},[2129,10310,2298],{"class":2139},[2129,10312,2144],{"class":2143},[2129,10314,8389],{"class":2188},[2129,10316,2217],{"class":2143},[2129,10318,6203],{"class":2201},[2129,10320,2256],{"class":2143},[2129,10322,10323,10325,10327,10329,10331,10333,10335],{"class":2131,"line":3526},[2129,10324,6233],{"class":2135},[2129,10326,8403],{"class":2201},[2129,10328,8406],{"class":2135},[2129,10330,2659],{"class":2422},[2129,10332,8411],{"class":2143},[2129,10334,6203],{"class":2201},[2129,10336,2905],{"class":2143},[2129,10338,10339,10342,10344,10346,10348,10350,10352,10354],{"class":2131,"line":3549},[2129,10340,10341],{"class":2201},"    vect",[2129,10343,178],{"class":2143},[2129,10345,8425],{"class":2188},[2129,10347,2217],{"class":2143},[2129,10349,8293],{"class":2201},[2129,10351,178],{"class":2143},[2129,10353,8316],{"class":2135},[2129,10355,7784],{"class":2143},[2129,10357,10358],{"class":2131,"line":3554},[2129,10359,6310],{"class":2143},[2129,10361,10362],{"class":2131,"line":3559},[2129,10363,10364],{"class":2201},"  vect\n",[2129,10366,10367],{"class":2131,"line":3584},[2129,10368,2353],{"class":2143},[2129,10370,10371],{"class":2131,"line":3599},[2129,10372,2179],{"emptyLinePlaceholder":1766},[2129,10374,10375,10377,10379,10381,10383,10385,10387,10389,10391,10393],{"class":2131,"line":3612},[2129,10376,2185],{"class":2135},[2129,10378,8459],{"class":2188},[2129,10380,2217],{"class":2143},[2129,10382,8293],{"class":2201},[2129,10384,2784],{"class":2143},[2129,10386,2321],{"class":2135},[2129,10388,8300],{"class":2139},[2129,10390,3106],{"class":2143},[2129,10392,7874],{"class":2139},[2129,10394,2905],{"class":2143},[2129,10396,10397,10399,10401,10403,10405,10407,10409,10411,10413,10415,10417,10419],{"class":2131,"line":3627},[2129,10398,10292],{"class":2135},[2129,10400,2264],{"class":2135},[2129,10402,8484],{"class":2201},[2129,10404,2206],{"class":2205},[2129,10406,8154],{"class":2139},[2129,10408,5940],{"class":2143},[2129,10410,8493],{"class":2201},[2129,10412,4472],{"class":2143},[2129,10414,2423],{"class":2422},[2129,10416,4477],{"class":2143},[2129,10418,6838],{"class":2422},[2129,10420,8504],{"class":2143},[2129,10422,10423,10425,10427,10429,10431,10433,10435],{"class":2131,"line":3632},[2129,10424,6233],{"class":2135},[2129,10426,6241],{"class":2201},[2129,10428,8406],{"class":2135},[2129,10430,2659],{"class":2422},[2129,10432,8411],{"class":2143},[2129,10434,6838],{"class":2422},[2129,10436,2905],{"class":2143},[2129,10438,10439,10441,10443,10445,10447,10449,10451,10453,10455],{"class":2131,"line":3637},[2129,10440,8267],{"class":2201},[2129,10442,8528],{"class":2143},[2129,10444,6250],{"class":2201},[2129,10446,2426],{"class":2143},[2129,10448,2511],{"class":2205},[2129,10450,8537],{"class":2201},[2129,10452,178],{"class":2143},[2129,10454,8316],{"class":2188},[2129,10456,2230],{"class":2143},[2129,10458,10459],{"class":2131,"line":6959},[2129,10460,6310],{"class":2143},[2129,10462,10463],{"class":2131,"line":6965},[2129,10464,10465],{"class":2201},"  sha256\n",[2129,10467,10468],{"class":2131,"line":6971},[2129,10469,2353],{"class":2143},[2129,10471,10472],{"class":2131,"line":6986},[2129,10473,2179],{"emptyLinePlaceholder":1766},[2129,10475,10476,10478,10480,10483],{"class":2131,"line":7008},[2129,10477,3691],{"class":2135},[2129,10479,3694],{"class":2135},[2129,10481,10482],{"class":2139}," TestObjectWrapper",[2129,10484,2905],{"class":2143},[2129,10486,10487,10490,10492,10494,10496,10498],{"class":2131,"line":7040},[2129,10488,10489],{"class":2201},"  test_objects",[2129,10491,2741],{"class":2143},[2129,10493,3049],{"class":2139},[2129,10495,2735],{"class":2143},[2129,10497,8745],{"class":2139},[2129,10499,8231],{"class":2143},[2129,10501,10502],{"class":2131,"line":7058},[2129,10503,2353],{"class":2143},[2129,10505,10506],{"class":2131,"line":7063},[2129,10507,2179],{"emptyLinePlaceholder":1766},[2129,10509,10510,10513,10515,10518],{"class":2131,"line":7068},[2129,10511,10512],{"class":2143},"#[napi(js_name ",[2129,10514,2511],{"class":2205},[2129,10516,10517],{"class":2220}," \"TestObjectWrapper\"",[2129,10519,7989],{"class":2143},[2129,10521,10522,10524,10526,10529],{"class":2131,"line":7074},[2129,10523,3691],{"class":2135},[2129,10525,3694],{"class":2135},[2129,10527,10528],{"class":2139}," JsTestObjectWrapper",[2129,10530,2905],{"class":2143},[2129,10532,10533,10536,10538,10541],{"class":2131,"line":7106},[2129,10534,10535],{"class":2201},"  test_object_wrapper",[2129,10537,2741],{"class":2143},[2129,10539,10540],{"class":2139},"TestObjectWrapper",[2129,10542,3022],{"class":2143},[2129,10544,10545],{"class":2131,"line":7112},[2129,10546,2353],{"class":2143},[2129,10548,10549],{"class":2131,"line":7123},[2129,10550,2179],{"emptyLinePlaceholder":1766},[2129,10552,10553],{"class":2131,"line":7129},[2129,10554,10555],{"class":2143},"#[napi]\n",[2129,10557,10558,10560,10562],{"class":2131,"line":7151},[2129,10559,2732],{"class":2135},[2129,10561,10528],{"class":2139},[2129,10563,2905],{"class":2143},[2129,10565,10566],{"class":2131,"line":7156},[2129,10567,10568],{"class":2143},"  #[napi(constructor)]\n",[2129,10570,10571,10574,10576,10578,10581,10583],{"class":2131,"line":7192},[2129,10572,10573],{"class":2135},"  pub",[2129,10575,4863],{"class":2135},[2129,10577,3094],{"class":2188},[2129,10579,10580],{"class":2143},"() -> ",[2129,10582,3109],{"class":2139},[2129,10584,2905],{"class":2143},[2129,10586,10587,10590],{"class":2131,"line":7231},[2129,10588,10589],{"class":2139},"    Self",[2129,10591,2905],{"class":2143},[2129,10593,10594,10597,10599,10601],{"class":2131,"line":7236},[2129,10595,10596],{"class":2201},"      test_object_wrapper",[2129,10598,2741],{"class":2143},[2129,10600,10540],{"class":2139},[2129,10602,2905],{"class":2143},[2129,10604,10605,10607,10609,10611,10613,10615],{"class":2131,"line":7242},[2129,10606,8783],{"class":2201},[2129,10608,2741],{"class":2143},[2129,10610,3049],{"class":2139},[2129,10612,2144],{"class":2143},[2129,10614,2248],{"class":2188},[2129,10616,6544],{"class":2143},[2129,10618,10619],{"class":2131,"line":7248},[2129,10620,10621],{"class":2143},"      },\n",[2129,10623,10624],{"class":2131,"line":7266},[2129,10625,2980],{"class":2143},[2129,10627,10628],{"class":2131,"line":7271},[2129,10629,6310],{"class":2143},[2129,10631,10632],{"class":2131,"line":7277},[2129,10633,2179],{"emptyLinePlaceholder":1766},[2129,10635,10636],{"class":2131,"line":7285},[2129,10637,10638],{"class":2143},"  #[napi]\n",[2129,10640,10641,10643,10645,10648,10650,10652,10654,10656,10658,10660,10663],{"class":2131,"line":7303},[2129,10642,10573],{"class":2135},[2129,10644,4863],{"class":2135},[2129,10646,10647],{"class":2188}," fill",[2129,10649,2318],{"class":2143},[2129,10651,2321],{"class":2135},[2129,10653,2776],{"class":2139},[2129,10655,931],{"class":2143},[2129,10657,8760],{"class":2201},[2129,10659,2741],{"class":2143},[2129,10661,10662],{"class":2139},"i32",[2129,10664,3177],{"class":2143},[2129,10666,10667,10669,10671,10673,10675,10677,10679,10681],{"class":2131,"line":7318},[2129,10668,2198],{"class":2135},[2129,10670,2264],{"class":2135},[2129,10672,8537],{"class":2201},[2129,10674,2206],{"class":2205},[2129,10676,7912],{"class":2139},[2129,10678,2144],{"class":2143},[2129,10680,8603],{"class":2188},[2129,10682,2230],{"class":2143},[2129,10684,10685],{"class":2131,"line":7323},[2129,10686,8610],{"class":2398},[2129,10688,10689,10691,10693,10695,10697,10699,10701,10703,10705],{"class":2131,"line":7346},[2129,10690,2198],{"class":2135},[2129,10692,8617],{"class":2201},[2129,10694,2206],{"class":2205},[2129,10696,8622],{"class":2139},[2129,10698,2144],{"class":2143},[2129,10700,8627],{"class":2188},[2129,10702,3541],{"class":2143},[2129,10704,2227],{"class":2188},[2129,10706,2230],{"class":2143},[2129,10708,10709],{"class":2131,"line":7351},[2129,10710,8638],{"class":2398},[2129,10712,10713,10715,10717,10719,10721,10723,10725,10727,10729,10731,10733],{"class":2131,"line":7357},[2129,10714,2198],{"class":2135},[2129,10716,8645],{"class":2201},[2129,10718,2206],{"class":2205},[2129,10720,2162],{"class":2139},[2129,10722,2144],{"class":2143},[2129,10724,8654],{"class":2139},[2129,10726,2144],{"class":2143},[2129,10728,8659],{"class":2139},[2129,10730,2144],{"class":2143},[2129,10732,6950],{"class":2188},[2129,10734,5066],{"class":2143},[2129,10736,10737,10740,10742,10744,10746,10748,10750,10752,10754],{"class":2131,"line":7380},[2129,10738,10739],{"class":2143},"      .",[2129,10741,8673],{"class":2188},[2129,10743,2217],{"class":2143},[2129,10745,5154],{"class":2139},[2129,10747,2144],{"class":2143},[2129,10749,8654],{"class":2139},[2129,10751,2144],{"class":2143},[2129,10753,8686],{"class":2422},[2129,10755,2975],{"class":2143},[2129,10757,10758,10760,10762],{"class":2131,"line":7411},[2129,10759,10739],{"class":2143},[2129,10761,2227],{"class":2188},[2129,10763,5066],{"class":2143},[2129,10765,10766,10768,10770],{"class":2131,"line":7417},[2129,10767,10739],{"class":2143},[2129,10769,8703],{"class":2188},[2129,10771,2230],{"class":2143},[2129,10773,10774],{"class":2131,"line":7440},[2129,10775,2179],{"emptyLinePlaceholder":1766},[2129,10777,10778,10781,10784,10786,10788,10790,10792,10794,10796,10798,10801,10803,10805],{"class":2131,"line":7473},[2129,10779,10780],{"class":2139},"    self",[2129,10782,10783],{"class":2143},".test_object_wrapper.test_objects ",[2129,10785,2511],{"class":2205},[2129,10787,2298],{"class":2139},[2129,10789,2144],{"class":2143},[2129,10791,8389],{"class":2188},[2129,10793,2217],{"class":2143},[2129,10795,8760],{"class":2201},[2129,10797,178],{"class":2143},[2129,10799,10800],{"class":2188},"try_into",[2129,10802,3541],{"class":2143},[2129,10804,2227],{"class":2188},[2129,10806,7784],{"class":2143},[2129,10808,10809],{"class":2131,"line":7484},[2129,10810,2179],{"emptyLinePlaceholder":1766},[2129,10812,10813,10815,10817,10819,10821,10823,10825],{"class":2131,"line":7492},[2129,10814,8400],{"class":2135},[2129,10816,8403],{"class":2201},[2129,10818,8406],{"class":2135},[2129,10820,2659],{"class":2422},[2129,10822,8411],{"class":2143},[2129,10824,8760],{"class":2201},[2129,10826,2905],{"class":2143},[2129,10828,10829,10832,10835,10837,10839,10841],{"class":2131,"line":7517},[2129,10830,10831],{"class":2139},"      self",[2129,10833,10834],{"class":2143},".test_object_wrapper.test_objects.",[2129,10836,8425],{"class":2188},[2129,10838,2217],{"class":2143},[2129,10840,8745],{"class":2139},[2129,10842,2905],{"class":2143},[2129,10844,10845,10848,10850,10852,10854,10856,10858,10860,10862],{"class":2131,"line":7523},[2129,10846,10847],{"class":2201},"        path",[2129,10849,2741],{"class":2143},[2129,10851,8803],{"class":2188},[2129,10853,2318],{"class":2143},[2129,10855,2321],{"class":2135},[2129,10857,8537],{"class":2201},[2129,10859,931],{"class":2143},[2129,10861,6415],{"class":2422},[2129,10863,5463],{"class":2143},[2129,10865,10866,10869,10871,10873],{"class":2131,"line":7528},[2129,10867,10868],{"class":2201},"        stats",[2129,10870,2741],{"class":2143},[2129,10872,8241],{"class":2139},[2129,10874,2905],{"class":2143},[2129,10876,10877,10880,10882,10884,10886,10888,10890],{"class":2131,"line":7533},[2129,10878,10879],{"class":2201},"          owner_id",[2129,10881,2741],{"class":2143},[2129,10883,8836],{"class":2188},[2129,10885,2318],{"class":2143},[2129,10887,2321],{"class":2135},[2129,10889,8537],{"class":2201},[2129,10891,5463],{"class":2143},[2129,10893,10894,10897,10899,10901,10903,10905,10907],{"class":2131,"line":7552},[2129,10895,10896],{"class":2201},"          group_id",[2129,10898,2741],{"class":2143},[2129,10900,8836],{"class":2188},[2129,10902,2318],{"class":2143},[2129,10904,2321],{"class":2135},[2129,10906,8537],{"class":2201},[2129,10908,5463],{"class":2143},[2129,10910,10911,10914,10916,10918,10920,10922,10924],{"class":2131,"line":7574},[2129,10912,10913],{"class":2201},"          size",[2129,10915,2741],{"class":2143},[2129,10917,8836],{"class":2188},[2129,10919,2318],{"class":2143},[2129,10921,2321],{"class":2135},[2129,10923,8537],{"class":2201},[2129,10925,5463],{"class":2143},[2129,10927,10928,10931,10933,10935,10937,10939,10941],{"class":2131,"line":7579},[2129,10929,10930],{"class":2201},"          compressed_size",[2129,10932,2741],{"class":2143},[2129,10934,8836],{"class":2188},[2129,10936,2318],{"class":2143},[2129,10938,2321],{"class":2135},[2129,10940,8537],{"class":2201},[2129,10942,5463],{"class":2143},[2129,10944,10945,10948,10950,10952,10954,10956,10958],{"class":2131,"line":9121},[2129,10946,10947],{"class":2201},"          last_read",[2129,10949,2741],{"class":2143},[2129,10951,8836],{"class":2188},[2129,10953,2318],{"class":2143},[2129,10955,2321],{"class":2135},[2129,10957,8537],{"class":2201},[2129,10959,5463],{"class":2143},[2129,10961,10962,10965,10967,10969,10971,10973,10975],{"class":2131,"line":9126},[2129,10963,10964],{"class":2201},"          last_modified",[2129,10966,2741],{"class":2143},[2129,10968,8836],{"class":2188},[2129,10970,2318],{"class":2143},[2129,10972,2321],{"class":2135},[2129,10974,8537],{"class":2201},[2129,10976,5463],{"class":2143},[2129,10978,10979,10982,10984,10986,10988,10990,10992],{"class":2131,"line":9152},[2129,10980,10981],{"class":2201},"          created",[2129,10983,2741],{"class":2143},[2129,10985,8836],{"class":2188},[2129,10987,2318],{"class":2143},[2129,10989,2321],{"class":2135},[2129,10991,8537],{"class":2201},[2129,10993,5463],{"class":2143},[2129,10995,10996,10999,11001,11003,11005,11007,11009],{"class":2131,"line":9173},[2129,10997,10998],{"class":2201},"          mode",[2129,11000,2741],{"class":2143},[2129,11002,8836],{"class":2188},[2129,11004,2318],{"class":2143},[2129,11006,2321],{"class":2135},[2129,11008,8537],{"class":2201},[2129,11010,5463],{"class":2143},[2129,11012,11013,11016,11018,11020,11022,11024,11026],{"class":2131,"line":9182},[2129,11014,11015],{"class":2201},"          dev",[2129,11017,2741],{"class":2143},[2129,11019,8836],{"class":2188},[2129,11021,2318],{"class":2143},[2129,11023,2321],{"class":2135},[2129,11025,8537],{"class":2201},[2129,11027,5463],{"class":2143},[2129,11029,11030,11033,11035,11037,11039,11041,11043],{"class":2131,"line":9191},[2129,11031,11032],{"class":2201},"          rdev",[2129,11034,2741],{"class":2143},[2129,11036,8836],{"class":2188},[2129,11038,2318],{"class":2143},[2129,11040,2321],{"class":2135},[2129,11042,8537],{"class":2201},[2129,11044,5463],{"class":2143},[2129,11046,11047,11050,11052,11054,11056,11058,11060],{"class":2131,"line":9197},[2129,11048,11049],{"class":2201},"          ino",[2129,11051,2741],{"class":2143},[2129,11053,8836],{"class":2188},[2129,11055,2318],{"class":2143},[2129,11057,2321],{"class":2135},[2129,11059,8537],{"class":2201},[2129,11061,5463],{"class":2143},[2129,11063,11064,11067,11069,11071,11073,11075,11077],{"class":2131,"line":9219},[2129,11065,11066],{"class":2201},"          nlink",[2129,11068,2741],{"class":2143},[2129,11070,8836],{"class":2188},[2129,11072,2318],{"class":2143},[2129,11074,2321],{"class":2135},[2129,11076,8537],{"class":2201},[2129,11078,5463],{"class":2143},[2129,11080,11081],{"class":2131,"line":9224},[2129,11082,11083],{"class":2143},"        },\n",[2129,11085,11086,11089,11091,11093],{"class":2131,"line":9229},[2129,11087,11088],{"class":2201},"        chunks",[2129,11090,2741],{"class":2143},[2129,11092,9046],{"class":2188},[2129,11094,9049],{"class":2143},[2129,11096,11097,11100,11102,11104,11106],{"class":2131,"line":9251},[2129,11098,11099],{"class":2188},"          generate_random_sha256",[2129,11101,2318],{"class":2143},[2129,11103,2321],{"class":2135},[2129,11105,8537],{"class":2201},[2129,11107,5463],{"class":2143},[2129,11109,11110,11112,11114,11116,11118],{"class":2131,"line":9257},[2129,11111,11099],{"class":2188},[2129,11113,2318],{"class":2143},[2129,11115,2321],{"class":2135},[2129,11117,8537],{"class":2201},[2129,11119,5463],{"class":2143},[2129,11121,11122,11124,11126,11128,11130],{"class":2131,"line":9264},[2129,11123,11099],{"class":2188},[2129,11125,2318],{"class":2143},[2129,11127,2321],{"class":2135},[2129,11129,8537],{"class":2201},[2129,11131,5463],{"class":2143},[2129,11133,11134],{"class":2131,"line":9272},[2129,11135,11136],{"class":2143},"        ],\n",[2129,11138,11139,11141,11143,11145,11147,11149,11151],{"class":2131,"line":9297},[2129,11140,8525],{"class":2201},[2129,11142,2741],{"class":2143},[2129,11144,9101],{"class":2188},[2129,11146,2318],{"class":2143},[2129,11148,2321],{"class":2135},[2129,11150,8537],{"class":2201},[2129,11152,5463],{"class":2143},[2129,11154,11155],{"class":2131,"line":9303},[2129,11156,11157],{"class":2143},"      });\n",[2129,11159,11160],{"class":2131,"line":9309},[2129,11161,2980],{"class":2143},[2129,11163,11164],{"class":2131,"line":9316},[2129,11165,2179],{"emptyLinePlaceholder":1766},[2129,11167,11168,11170,11172,11174,11176,11178,11180,11182,11184,11186,11188],{"class":2131,"line":9324},[2129,11169,2198],{"class":2135},[2129,11171,9131],{"class":2201},[2129,11173,2206],{"class":2205},[2129,11175,2162],{"class":2139},[2129,11177,2144],{"class":2143},[2129,11179,8654],{"class":2139},[2129,11181,2144],{"class":2143},[2129,11183,8659],{"class":2139},[2129,11185,2144],{"class":2143},[2129,11187,6950],{"class":2188},[2129,11189,5066],{"class":2143},[2129,11191,11192,11194,11196,11198,11200,11202,11204,11206,11208],{"class":2131,"line":9349},[2129,11193,10739],{"class":2143},[2129,11195,8673],{"class":2188},[2129,11197,2217],{"class":2143},[2129,11199,5154],{"class":2139},[2129,11201,2144],{"class":2143},[2129,11203,8654],{"class":2139},[2129,11205,2144],{"class":2143},[2129,11207,8686],{"class":2422},[2129,11209,2975],{"class":2143},[2129,11211,11212,11214,11216],{"class":2131,"line":9354},[2129,11213,10739],{"class":2143},[2129,11215,2227],{"class":2188},[2129,11217,5066],{"class":2143},[2129,11219,11220,11222,11224],{"class":2131,"line":9359},[2129,11221,10739],{"class":2143},[2129,11223,8703],{"class":2188},[2129,11225,2230],{"class":2143},[2129,11227,11228],{"class":2131,"line":9365},[2129,11229,9194],{"class":2398},[2129,11231,11232,11234,11236,11238,11240,11242,11244,11246],{"class":2131,"line":9388},[2129,11233,2335],{"class":2188},[2129,11235,2217],{"class":2143},[2129,11237,9204],{"class":2220},[2129,11239,931],{"class":2143},[2129,11241,9209],{"class":2201},[2129,11243,9212],{"class":2143},[2129,11245,6950],{"class":2201},[2129,11247,2256],{"class":2143},[2129,11249,11250],{"class":2131,"line":9401},[2129,11251,2179],{"emptyLinePlaceholder":1766},[2129,11253,11254],{"class":2131,"line":9413},[2129,11255,8610],{"class":2398},[2129,11257,11258,11260,11262,11264,11266,11268,11270,11272,11274],{"class":2131,"line":9418},[2129,11259,2198],{"class":2135},[2129,11261,9234],{"class":2201},[2129,11263,2206],{"class":2205},[2129,11265,8622],{"class":2139},[2129,11267,2144],{"class":2143},[2129,11269,8627],{"class":2188},[2129,11271,3541],{"class":2143},[2129,11273,2227],{"class":2188},[2129,11275,2230],{"class":2143},[2129,11277,11278],{"class":2131,"line":9423},[2129,11279,9254],{"class":2398},[2129,11281,11282,11284],{"class":2131,"line":9429},[2129,11283,2335],{"class":2188},[2129,11285,4631],{"class":2143},[2129,11287,11288,11291],{"class":2131,"line":9463},[2129,11289,11290],{"class":2220},"      \"Memory consumption after creation: {}\"",[2129,11292,3022],{"class":2143},[2129,11294,11295,11298,11300,11302,11304,11306,11308,11310,11312,11314],{"class":2131,"line":9489},[2129,11296,11297],{"class":2188},"      format_size",[2129,11299,7214],{"class":2143},[2129,11301,9280],{"class":2201},[2129,11303,9283],{"class":2143},[2129,11305,9286],{"class":2201},[2129,11307,9289],{"class":2143},[2129,11309,9292],{"class":2422},[2129,11311,931],{"class":2143},[2129,11313,7892],{"class":2422},[2129,11315,2975],{"class":2143},[2129,11317,11318],{"class":2131,"line":9508},[2129,11319,9300],{"class":2143},[2129,11321,11322],{"class":2131,"line":9536},[2129,11323,9306],{"class":2398},[2129,11325,11326,11328],{"class":2131,"line":9541},[2129,11327,2335],{"class":2188},[2129,11329,4631],{"class":2143},[2129,11331,11332,11335],{"class":2131,"line":9546},[2129,11333,11334],{"class":2220},"      \"Average memory consumption per object: {}\"",[2129,11336,3022],{"class":2143},[2129,11338,11339,11341],{"class":2131,"line":9552},[2129,11340,11297],{"class":2188},[2129,11342,4631],{"class":2143},[2129,11344,11345,11348,11350,11352,11354,11356,11358,11360,11362,11364,11367],{"class":2131,"line":9578},[2129,11346,11347],{"class":2143},"        ((",[2129,11349,9280],{"class":2201},[2129,11351,9283],{"class":2143},[2129,11353,9286],{"class":2201},[2129,11355,9289],{"class":2143},[2129,11357,9292],{"class":2422},[2129,11359,9342],{"class":2143},[2129,11361,8760],{"class":2201},[2129,11363,9768],{"class":2135},[2129,11365,11366],{"class":2139}," usize",[2129,11368,3022],{"class":2143},[2129,11370,11371],{"class":2131,"line":9599},[2129,11372,11373],{"class":2422},"        DECIMAL\n",[2129,11375,11376],{"class":2131,"line":9608},[2129,11377,11378],{"class":2143},"      )\n",[2129,11380,11381],{"class":2131,"line":9617},[2129,11382,9300],{"class":2143},[2129,11384,11385],{"class":2131,"line":9622},[2129,11386,6310],{"class":2143},[2129,11388,11389],{"class":2131,"line":9629},[2129,11390,2179],{"emptyLinePlaceholder":1766},[2129,11392,11393,11396,11398,11401],{"class":2131,"line":9637},[2129,11394,11395],{"class":2143},"  #[napi(js_name ",[2129,11397,2511],{"class":2205},[2129,11399,11400],{"class":2220}," \"toString\"",[2129,11402,7989],{"class":2143},[2129,11404,11405,11407,11409,11412,11414,11416,11418,11421],{"class":2131,"line":9648},[2129,11406,10573],{"class":2135},[2129,11408,4863],{"class":2135},[2129,11410,11411],{"class":2188}," to_string",[2129,11413,2318],{"class":2143},[2129,11415,3533],{"class":2139},[2129,11417,3106],{"class":2143},[2129,11419,11420],{"class":2139},"String",[2129,11422,2905],{"class":2143},[2129,11424,11425,11428],{"class":2131,"line":9653},[2129,11426,11427],{"class":2188},"    format!",[2129,11429,4631],{"class":2143},[2129,11431,11432,11435],{"class":2131,"line":9658},[2129,11433,11434],{"class":2220},"      \"TestObjectWrapper {{ test_objects: {} }}\"",[2129,11436,3022],{"class":2143},[2129,11438,11439,11441,11443,11445],{"class":2131,"line":9664},[2129,11440,10831],{"class":2139},[2129,11442,10834],{"class":2143},[2129,11444,2853],{"class":2188},[2129,11446,5066],{"class":2143},[2129,11448,11449],{"class":2131,"line":9695},[2129,11450,11451],{"class":2143},"    )\n",[2129,11453,11454],{"class":2131,"line":9702},[2129,11455,6310],{"class":2143},[2129,11457,11458],{"class":2131,"line":9710},[2129,11459,2353],{"class":2143},[11,11461,11462],{},"Pour la parte Node.JS :",[2122,11464,11466],{"className":6105,"code":11465,"language":6107,"meta":1645,"style":1645},"const test = require(\".\u002Findex.js\");\nconst filesize = require(\"filesize.js\");\nconst fs = require(\"fs\");\nconst { serialize, deserialize } = require(\"v8\");\n\u002F\u002F Start by consuming memory with big object\n\n\u002F\u002F Run GC\nglobal.gc();\n\u002F\u002F Get memory consumption before\nconst memoryBefore = process.memoryUsage().heapUsed;\n\nconst time = Date.now();\n\n\u002F\u002F Create objects\nconst nbObjects = 1_300_000;\nconst testArray = new test.TestObjectWrapper();\ntestArray.fill(nbObjects);\n\n\u002F\u002F Bench creation\nconsole.log(\"Creation time: \", Date.now() - time);\n\n\u002F\u002F Run GC\n\u002F\u002F Get memory consumption after\nconst memoryAfter = process.memoryUsage().heapUsed;\n\n\u002F\u002F Print memory consumption\nconsole.log(\n  \"Memory consumption in JS: \",\n  filesize.default(memoryAfter - memoryBefore)\n);\n\nconsole.log(testArray.toString());\n",[183,11467,11468,11486,11502,11518,11542,11547,11551,11556,11566,11571,11591,11595,11611,11615,11620,11632,11650,11666,11670,11675,11703,11707,11711,11716,11736,11740,11745,11755,11762,11781,11785,11789],{"__ignoreMap":1645},[2129,11469,11470,11472,11475,11477,11479,11481,11484],{"class":2131,"line":2132},[2129,11471,6114],{"class":2135},[2129,11473,11474],{"class":2139}," test",[2129,11476,2206],{"class":2205},[2129,11478,6122],{"class":2188},[2129,11480,2217],{"class":2143},[2129,11482,11483],{"class":2220},"\".\u002Findex.js\"",[2129,11485,2256],{"class":2143},[2129,11487,11488,11490,11492,11494,11496,11498,11500],{"class":2131,"line":1646},[2129,11489,6114],{"class":2135},[2129,11491,6117],{"class":2139},[2129,11493,2206],{"class":2205},[2129,11495,6122],{"class":2188},[2129,11497,2217],{"class":2143},[2129,11499,6127],{"class":2220},[2129,11501,2256],{"class":2143},[2129,11503,11504,11506,11508,11510,11512,11514,11516],{"class":2131,"line":1651},[2129,11505,6114],{"class":2135},[2129,11507,6136],{"class":2139},[2129,11509,2206],{"class":2205},[2129,11511,6122],{"class":2188},[2129,11513,2217],{"class":2143},[2129,11515,6145],{"class":2220},[2129,11517,2256],{"class":2143},[2129,11519,11520,11522,11524,11526,11528,11530,11532,11534,11536,11538,11540],{"class":2131,"line":2182},[2129,11521,6114],{"class":2135},[2129,11523,5940],{"class":2143},[2129,11525,6161],{"class":2139},[2129,11527,931],{"class":2143},[2129,11529,6166],{"class":2139},[2129,11531,6169],{"class":2143},[2129,11533,2511],{"class":2205},[2129,11535,6122],{"class":2188},[2129,11537,2217],{"class":2143},[2129,11539,6178],{"class":2220},[2129,11541,2256],{"class":2143},[2129,11543,11544],{"class":2131,"line":2195},[2129,11545,11546],{"class":2398},"\u002F\u002F Start by consuming memory with big object\n",[2129,11548,11549],{"class":2131,"line":2233},[2129,11550,2179],{"emptyLinePlaceholder":1766},[2129,11552,11553],{"class":2131,"line":2259},[2129,11554,11555],{"class":2398},"\u002F\u002F Run GC\n",[2129,11557,11558,11560,11562,11564],{"class":2131,"line":2286},[2129,11559,6892],{"class":2139},[2129,11561,178],{"class":2143},[2129,11563,6897],{"class":2188},[2129,11565,2230],{"class":2143},[2129,11567,11568],{"class":2131,"line":2307},[2129,11569,11570],{"class":2398},"\u002F\u002F Get memory consumption before\n",[2129,11572,11573,11575,11577,11579,11581,11583,11585,11587,11589],{"class":2131,"line":2332},[2129,11574,6114],{"class":2135},[2129,11576,6911],{"class":2139},[2129,11578,2206],{"class":2205},[2129,11580,6916],{"class":2139},[2129,11582,178],{"class":2143},[2129,11584,6921],{"class":2188},[2129,11586,3541],{"class":2143},[2129,11588,6926],{"class":2201},[2129,11590,2155],{"class":2143},[2129,11592,11593],{"class":2131,"line":2350},[2129,11594,2179],{"emptyLinePlaceholder":1766},[2129,11596,11597,11599,11601,11603,11605,11607,11609],{"class":2131,"line":2564},[2129,11598,6114],{"class":2135},[2129,11600,6940],{"class":2139},[2129,11602,2206],{"class":2205},[2129,11604,6945],{"class":2139},[2129,11606,178],{"class":2143},[2129,11608,6950],{"class":2188},[2129,11610,2230],{"class":2143},[2129,11612,11613],{"class":2131,"line":2570},[2129,11614,2179],{"emptyLinePlaceholder":1766},[2129,11616,11617],{"class":2131,"line":2576},[2129,11618,11619],{"class":2398},"\u002F\u002F Create objects\n",[2129,11621,11622,11624,11626,11628,11630],{"class":2131,"line":2582},[2129,11623,6114],{"class":2135},[2129,11625,6976],{"class":2139},[2129,11627,2206],{"class":2205},[2129,11629,6981],{"class":2422},[2129,11631,2155],{"class":2143},[2129,11633,11634,11636,11638,11640,11642,11644,11646,11648],{"class":2131,"line":2587},[2129,11635,6114],{"class":2135},[2129,11637,6991],{"class":2139},[2129,11639,2206],{"class":2205},[2129,11641,3094],{"class":2135},[2129,11643,11474],{"class":2139},[2129,11645,178],{"class":2143},[2129,11647,10540],{"class":2188},[2129,11649,2230],{"class":2143},[2129,11651,11652,11655,11657,11660,11662,11664],{"class":2131,"line":2604},[2129,11653,11654],{"class":2139},"testArray",[2129,11656,178],{"class":2143},[2129,11658,11659],{"class":2188},"fill",[2129,11661,2217],{"class":2143},[2129,11663,7003],{"class":2201},[2129,11665,2256],{"class":2143},[2129,11667,11668],{"class":2131,"line":2610},[2129,11669,2179],{"emptyLinePlaceholder":1766},[2129,11671,11672],{"class":2131,"line":2644},[2129,11673,11674],{"class":2398},"\u002F\u002F Bench creation\n",[2129,11676,11677,11679,11681,11683,11685,11687,11689,11691,11693,11695,11697,11699,11701],{"class":2131,"line":2664},[2129,11678,7077],{"class":2139},[2129,11680,178],{"class":2143},[2129,11682,7082],{"class":2188},[2129,11684,2217],{"class":2143},[2129,11686,7087],{"class":2220},[2129,11688,931],{"class":2143},[2129,11690,35],{"class":2139},[2129,11692,178],{"class":2143},[2129,11694,6950],{"class":2188},[2129,11696,2856],{"class":2143},[2129,11698,2627],{"class":2205},[2129,11700,6940],{"class":2201},[2129,11702,2256],{"class":2143},[2129,11704,11705],{"class":2131,"line":3216},[2129,11706,2179],{"emptyLinePlaceholder":1766},[2129,11708,11709],{"class":2131,"line":1776},[2129,11710,11555],{"class":2398},[2129,11712,11713],{"class":2131,"line":3248},[2129,11714,11715],{"class":2398},"\u002F\u002F Get memory consumption after\n",[2129,11717,11718,11720,11722,11724,11726,11728,11730,11732,11734],{"class":2131,"line":3278},[2129,11719,6114],{"class":2135},[2129,11721,7134],{"class":2139},[2129,11723,2206],{"class":2205},[2129,11725,6916],{"class":2139},[2129,11727,178],{"class":2143},[2129,11729,6921],{"class":2188},[2129,11731,3541],{"class":2143},[2129,11733,6926],{"class":2201},[2129,11735,2155],{"class":2143},[2129,11737,11738],{"class":2131,"line":3293},[2129,11739,2179],{"emptyLinePlaceholder":1766},[2129,11741,11742],{"class":2131,"line":3310},[2129,11743,11744],{"class":2398},"\u002F\u002F Print memory consumption\n",[2129,11746,11747,11749,11751,11753],{"class":2131,"line":3330},[2129,11748,7077],{"class":2139},[2129,11750,178],{"class":2143},[2129,11752,7082],{"class":2188},[2129,11754,4631],{"class":2143},[2129,11756,11757,11760],{"class":2131,"line":3335},[2129,11758,11759],{"class":2220},"  \"Memory consumption in JS: \"",[2129,11761,3022],{"class":2143},[2129,11763,11764,11767,11769,11771,11773,11775,11777,11779],{"class":2131,"line":3359},[2129,11765,11766],{"class":2139},"  filesize",[2129,11768,178],{"class":2143},[2129,11770,7177],{"class":2188},[2129,11772,2217],{"class":2143},[2129,11774,7182],{"class":2201},[2129,11776,7185],{"class":2205},[2129,11778,6911],{"class":2201},[2129,11780,2975],{"class":2143},[2129,11782,11783],{"class":2131,"line":3374},[2129,11784,2256],{"class":2143},[2129,11786,11787],{"class":2131,"line":3379},[2129,11788,2179],{"emptyLinePlaceholder":1766},[2129,11790,11791,11793,11795,11797,11799,11801,11803,11806],{"class":2131,"line":3415},[2129,11792,7077],{"class":2139},[2129,11794,178],{"class":2143},[2129,11796,7082],{"class":2188},[2129,11798,2217],{"class":2143},[2129,11800,11654],{"class":2139},[2129,11802,178],{"class":2143},[2129,11804,11805],{"class":2188},"toString",[2129,11807,7784],{"class":2143},[11,11809,11810],{},"Voici le résultat:",[21,11812,11813,11821],{},[24,11814,11815],{},[27,11816,11817,11819],{},[30,11818],{},[30,11820],{},[40,11822,11823,11829,11835,11842],{},[27,11824,11825,11827],{},[45,11826,7611],{},[45,11828,7614],{},[27,11830,11831,11833],{},[45,11832,7619],{},[45,11834,9835],{},[27,11836,11837,11839],{},[45,11838,7627],{},[45,11840,11841],{},"520,31 Mo",[27,11843,11844,11846],{},[45,11845,7635],{},[45,11847,11848],{},"400 octets",[11,11850,11851],{},"Parfait, le module écrit en Rust va nous permettre de réduire la consommation mémoire de notre application et améliorer\nses performances.",[127,11853,11855,11858],{"id":11854},"conclusion-wasm",[11856,11857,1620],"del",{}," - Wasm",[11,11860,11861],{},"Une alternative possible à l'écriture d'un module natif en Rust est l'utilisation de WebAssembly. WebAssembly est un\nlangage de bas niveau qui permet d'écrire des modules qui seront exécutés dans un environnement sécurisé. Il est\npossible d'écrire des modules en Rust qui seront compilés en WebAssembly.",[11,11863,11864,11865,178],{},"Pour compiler notre module en WebAssembly, nous allons utiliser le compilateur ",[48,11866,11869],{"href":11867,"rel":11868},"https:\u002F\u002Frustwasm.github.io\u002Fwasm-pack\u002F",[346],"wasm-pack",[11,11871,11872],{},"La partie Rust du module n'est pas très différente de la version native. La seule différence est que nous devons\nutiliser le crate wasm-bindgen pour pouvoir utiliser notre module dans Node.JS.",[11,11874,11875,11876,11880],{},"Là encore, la documentation est très bien faite (",[48,11877,11878],{"href":11878,"rel":11879},"https:\u002F\u002Frustwasm.github.io\u002Fdocs\u002Fbook\u002F",[346],"). Je ne vais pas détailler ici\nla création de ce module.",[2122,11882,11884],{"className":2124,"code":11883,"language":1774,"meta":1645,"style":1645},"mod utils;\n\nuse rand::{rngs::ThreadRng, Rng};\n\nuse wasm_bindgen::prelude::*;\n\n\u002F\u002F When the `wee_alloc` feature is enabled, use `wee_alloc` as the global\n\u002F\u002F allocator.\n#[cfg(feature = \"wee_alloc\")]\n#[global_allocator]\nstatic ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;\n\n#[wasm_bindgen]\nstruct Stats {\n    owner_id: u64,\n    group_id: u64,\n    size: u64,\n    compressed_size: u64,\n    last_read: u64,\n    last_modified: u64,\n    created: u64,\n    mode: u64,\n    dev: u64,\n    rdev: u64,\n    ino: u64,\n    nlink: u64,\n}\n\n#[wasm_bindgen]\nstruct Sha256 {\n    data: [u8; 32],\n}\n\n#[wasm_bindgen]\nstruct TestObject {\n    path: Vec\u003Cu8>,\n    stats: Stats,\n\n    chunks: Vec\u003CSha256>,\n    sha256: Sha256,\n}\n\nfn generate_random_number(rng: &mut ThreadRng) -> u64 {\n    rng.gen()\n}\n\nfn generate_random_vect(rng: &mut ThreadRng, size: usize) -> Vec\u003Cu8> {\n    let mut vect: Vec\u003Cu8> = Vec::with_capacity(size);\n    for _ in 0..size {\n        vect.push(rng.gen());\n    }\n    vect\n}\n\nfn generate_random_sha256(rng: &mut ThreadRng) -> Sha256 {\n    let mut sha256 = Sha256 { data: [0; 32] };\n    for i in 0..32 {\n        sha256.data[i] = rng.gen();\n    }\n    sha256\n}\n\n#[wasm_bindgen]\npub struct TestObjectWrapper {\n    test_objects: Vec\u003CTestObject>,\n}\n\n#[wasm_bindgen]\nimpl TestObjectWrapper {\n    #[wasm_bindgen(constructor)]\n    pub fn new() -> Self {\n        Self {\n            test_objects: Vec::new(),\n        }\n    }\n\n    #[wasm_bindgen]\n    pub fn fill(&mut self, nb_object: i32) {\n        let mut rng = rand::thread_rng();\n        \u002F\u002F Get the current time in ms\n        self.test_objects = Vec::with_capacity(nb_object as usize);\n\n        for _ in 0..nb_object {\n            self.test_objects.push(TestObject {\n                path: generate_random_vect(&mut rng, 100),\n                stats: Stats {\n                    owner_id: generate_random_number(&mut rng),\n                    group_id: generate_random_number(&mut rng),\n                    size: generate_random_number(&mut rng),\n                    compressed_size: generate_random_number(&mut rng),\n                    last_read: generate_random_number(&mut rng),\n                    last_modified: generate_random_number(&mut rng),\n                    created: generate_random_number(&mut rng),\n                    mode: generate_random_number(&mut rng),\n                    dev: generate_random_number(&mut rng),\n                    rdev: generate_random_number(&mut rng),\n                    ino: generate_random_number(&mut rng),\n                    nlink: generate_random_number(&mut rng),\n                },\n                chunks: vec![\n                    generate_random_sha256(&mut rng),\n                    generate_random_sha256(&mut rng),\n                    generate_random_sha256(&mut rng),\n                ],\n                sha256: generate_random_sha256(&mut rng),\n            });\n        }\n    }\n\n    #[wasm_bindgen(js_name = toString)]\n    pub fn to_string(&self) -> String {\n        format!(\n            \"TestObjectWrapper {{ test_objects: {} }}\",\n            self.test_objects.len()\n        )\n    }\n}\n",[183,11885,11886,11894,11898,11918,11922,11937,11941,11946,11951,11963,11968,12001,12005,12010,12018,12028,12038,12048,12058,12068,12078,12088,12098,12108,12118,12128,12138,12142,12146,12150,12158,12172,12176,12180,12184,12192,12206,12216,12220,12234,12244,12248,12252,12274,12284,12288,12292,12326,12358,12374,12392,12396,12400,12404,12408,12430,12456,12472,12492,12496,12500,12504,12508,12512,12522,12537,12541,12545,12549,12557,12562,12577,12583,12598,12602,12606,12610,12615,12639,12657,12662,12687,12691,12708,12723,12744,12755,12772,12789,12806,12823,12840,12857,12874,12891,12908,12925,12942,12959,12964,12975,12988,13000,13012,13017,13034,13039,13043,13047,13051,13061,13079,13086,13093,13103,13108,13112],{"__ignoreMap":1645},[2129,11887,11888,11891],{"class":2131,"line":2132},[2129,11889,11890],{"class":2135},"mod",[2129,11892,11893],{"class":2143}," utils;\n",[2129,11895,11896],{"class":2131,"line":1646},[2129,11897,2179],{"emptyLinePlaceholder":1766},[2129,11899,11900,11902,11904,11906,11908,11910,11912,11914,11916],{"class":2131,"line":1651},[2129,11901,2136],{"class":2135},[2129,11903,7912],{"class":2139},[2129,11905,7915],{"class":2143},[2129,11907,7918],{"class":2139},[2129,11909,2144],{"class":2143},[2129,11911,7923],{"class":2139},[2129,11913,931],{"class":2143},[2129,11915,7928],{"class":2139},[2129,11917,7895],{"class":2143},[2129,11919,11920],{"class":2131,"line":2182},[2129,11921,2179],{"emptyLinePlaceholder":1766},[2129,11923,11924,11926,11929,11931,11934],{"class":2131,"line":2195},[2129,11925,2136],{"class":2135},[2129,11927,11928],{"class":2139}," wasm_bindgen",[2129,11930,2144],{"class":2143},[2129,11932,11933],{"class":2139},"prelude",[2129,11935,11936],{"class":2143},"::*;\n",[2129,11938,11939],{"class":2131,"line":2233},[2129,11940,2179],{"emptyLinePlaceholder":1766},[2129,11942,11943],{"class":2131,"line":2259},[2129,11944,11945],{"class":2398},"\u002F\u002F When the `wee_alloc` feature is enabled, use `wee_alloc` as the global\n",[2129,11947,11948],{"class":2131,"line":2286},[2129,11949,11950],{"class":2398},"\u002F\u002F allocator.\n",[2129,11952,11953,11956,11958,11961],{"class":2131,"line":2307},[2129,11954,11955],{"class":2143},"#[cfg(feature ",[2129,11957,2511],{"class":2205},[2129,11959,11960],{"class":2220}," \"wee_alloc\"",[2129,11962,7989],{"class":2143},[2129,11964,11965],{"class":2131,"line":2332},[2129,11966,11967],{"class":2143},"#[global_allocator]\n",[2129,11969,11970,11972,11975,11977,11980,11982,11985,11987,11990,11992,11994,11996,11999],{"class":2131,"line":2350},[2129,11971,5992],{"class":2135},[2129,11973,11974],{"class":2422}," ALLOC",[2129,11976,2741],{"class":2143},[2129,11978,11979],{"class":2139},"wee_alloc",[2129,11981,2144],{"class":2143},[2129,11983,11984],{"class":2139},"WeeAlloc",[2129,11986,2206],{"class":2205},[2129,11988,11989],{"class":2139}," wee_alloc",[2129,11991,2144],{"class":2143},[2129,11993,11984],{"class":2139},[2129,11995,2144],{"class":2143},[2129,11997,11998],{"class":2422},"INIT",[2129,12000,2155],{"class":2143},[2129,12002,12003],{"class":2131,"line":2564},[2129,12004,2179],{"emptyLinePlaceholder":1766},[2129,12006,12007],{"class":2131,"line":2570},[2129,12008,12009],{"class":2143},"#[wasm_bindgen]\n",[2129,12011,12012,12014,12016],{"class":2131,"line":2576},[2129,12013,2997],{"class":2135},[2129,12015,7996],{"class":2139},[2129,12017,2905],{"class":2143},[2129,12019,12020,12022,12024,12026],{"class":2131,"line":2582},[2129,12021,8003],{"class":2201},[2129,12023,2741],{"class":2143},[2129,12025,4418],{"class":2139},[2129,12027,3022],{"class":2143},[2129,12029,12030,12032,12034,12036],{"class":2131,"line":2587},[2129,12031,8014],{"class":2201},[2129,12033,2741],{"class":2143},[2129,12035,4418],{"class":2139},[2129,12037,3022],{"class":2143},[2129,12039,12040,12042,12044,12046],{"class":2131,"line":2604},[2129,12041,6499],{"class":2201},[2129,12043,2741],{"class":2143},[2129,12045,4418],{"class":2139},[2129,12047,3022],{"class":2143},[2129,12049,12050,12052,12054,12056],{"class":2131,"line":2610},[2129,12051,8035],{"class":2201},[2129,12053,2741],{"class":2143},[2129,12055,4418],{"class":2139},[2129,12057,3022],{"class":2143},[2129,12059,12060,12062,12064,12066],{"class":2131,"line":2644},[2129,12061,8046],{"class":2201},[2129,12063,2741],{"class":2143},[2129,12065,4418],{"class":2139},[2129,12067,3022],{"class":2143},[2129,12069,12070,12072,12074,12076],{"class":2131,"line":2664},[2129,12071,8057],{"class":2201},[2129,12073,2741],{"class":2143},[2129,12075,4418],{"class":2139},[2129,12077,3022],{"class":2143},[2129,12079,12080,12082,12084,12086],{"class":2131,"line":3216},[2129,12081,6638],{"class":2201},[2129,12083,2741],{"class":2143},[2129,12085,4418],{"class":2139},[2129,12087,3022],{"class":2143},[2129,12089,12090,12092,12094,12096],{"class":2131,"line":1776},[2129,12091,6669],{"class":2201},[2129,12093,2741],{"class":2143},[2129,12095,4418],{"class":2139},[2129,12097,3022],{"class":2143},[2129,12099,12100,12102,12104,12106],{"class":2131,"line":3248},[2129,12101,6700],{"class":2201},[2129,12103,2741],{"class":2143},[2129,12105,4418],{"class":2139},[2129,12107,3022],{"class":2143},[2129,12109,12110,12112,12114,12116],{"class":2131,"line":3278},[2129,12111,6731],{"class":2201},[2129,12113,2741],{"class":2143},[2129,12115,4418],{"class":2139},[2129,12117,3022],{"class":2143},[2129,12119,12120,12122,12124,12126],{"class":2131,"line":3293},[2129,12121,6762],{"class":2201},[2129,12123,2741],{"class":2143},[2129,12125,4418],{"class":2139},[2129,12127,3022],{"class":2143},[2129,12129,12130,12132,12134,12136],{"class":2131,"line":3310},[2129,12131,6793],{"class":2201},[2129,12133,2741],{"class":2143},[2129,12135,4418],{"class":2139},[2129,12137,3022],{"class":2143},[2129,12139,12140],{"class":2131,"line":3330},[2129,12141,2353],{"class":2143},[2129,12143,12144],{"class":2131,"line":3335},[2129,12145,2179],{"emptyLinePlaceholder":1766},[2129,12147,12148],{"class":2131,"line":3359},[2129,12149,12009],{"class":2143},[2129,12151,12152,12154,12156],{"class":2131,"line":3374},[2129,12153,2997],{"class":2135},[2129,12155,8154],{"class":2139},[2129,12157,2905],{"class":2143},[2129,12159,12160,12162,12164,12166,12168,12170],{"class":2131,"line":3379},[2129,12161,8161],{"class":2201},[2129,12163,4472],{"class":2143},[2129,12165,2792],{"class":2139},[2129,12167,4477],{"class":2143},[2129,12169,6838],{"class":2422},[2129,12171,5494],{"class":2143},[2129,12173,12174],{"class":2131,"line":3415},[2129,12175,2353],{"class":2143},[2129,12177,12178],{"class":2131,"line":3434},[2129,12179,2179],{"emptyLinePlaceholder":1766},[2129,12181,12182],{"class":2131,"line":3459},[2129,12183,12009],{"class":2143},[2129,12185,12186,12188,12190],{"class":2131,"line":3465},[2129,12187,2997],{"class":2135},[2129,12189,8202],{"class":2139},[2129,12191,2905],{"class":2143},[2129,12193,12194,12196,12198,12200,12202,12204],{"class":2131,"line":3480},[2129,12195,5883],{"class":2201},[2129,12197,2741],{"class":2143},[2129,12199,3049],{"class":2139},[2129,12201,2735],{"class":2143},[2129,12203,2792],{"class":2139},[2129,12205,8231],{"class":2143},[2129,12207,12208,12210,12212,12214],{"class":2131,"line":3486},[2129,12209,8236],{"class":2201},[2129,12211,2741],{"class":2143},[2129,12213,8241],{"class":2139},[2129,12215,3022],{"class":2143},[2129,12217,12218],{"class":2131,"line":3492},[2129,12219,2179],{"emptyLinePlaceholder":1766},[2129,12221,12222,12224,12226,12228,12230,12232],{"class":2131,"line":3497},[2129,12223,8252],{"class":2201},[2129,12225,2741],{"class":2143},[2129,12227,3049],{"class":2139},[2129,12229,2735],{"class":2143},[2129,12231,7874],{"class":2139},[2129,12233,8231],{"class":2143},[2129,12235,12236,12238,12240,12242],{"class":2131,"line":3516},[2129,12237,8267],{"class":2201},[2129,12239,2741],{"class":2143},[2129,12241,7874],{"class":2139},[2129,12243,3022],{"class":2143},[2129,12245,12246],{"class":2131,"line":3521},[2129,12247,2353],{"class":2143},[2129,12249,12250],{"class":2131,"line":3526},[2129,12251,2179],{"emptyLinePlaceholder":1766},[2129,12253,12254,12256,12258,12260,12262,12264,12266,12268,12270,12272],{"class":2131,"line":3549},[2129,12255,2185],{"class":2135},[2129,12257,8288],{"class":2188},[2129,12259,2217],{"class":2143},[2129,12261,8293],{"class":2201},[2129,12263,2784],{"class":2143},[2129,12265,2321],{"class":2135},[2129,12267,8300],{"class":2139},[2129,12269,3106],{"class":2143},[2129,12271,4418],{"class":2139},[2129,12273,2905],{"class":2143},[2129,12275,12276,12278,12280,12282],{"class":2131,"line":3554},[2129,12277,8311],{"class":2201},[2129,12279,178],{"class":2143},[2129,12281,8316],{"class":2188},[2129,12283,5066],{"class":2143},[2129,12285,12286],{"class":2131,"line":3559},[2129,12287,2353],{"class":2143},[2129,12289,12290],{"class":2131,"line":3584},[2129,12291,2179],{"emptyLinePlaceholder":1766},[2129,12293,12294,12296,12298,12300,12302,12304,12306,12308,12310,12312,12314,12316,12318,12320,12322,12324],{"class":2131,"line":3599},[2129,12295,2185],{"class":2135},[2129,12297,8333],{"class":2188},[2129,12299,2217],{"class":2143},[2129,12301,8293],{"class":2201},[2129,12303,2784],{"class":2143},[2129,12305,2321],{"class":2135},[2129,12307,8300],{"class":2139},[2129,12309,931],{"class":2143},[2129,12311,6203],{"class":2201},[2129,12313,2741],{"class":2143},[2129,12315,2808],{"class":2139},[2129,12317,3106],{"class":2143},[2129,12319,3049],{"class":2139},[2129,12321,2735],{"class":2143},[2129,12323,2792],{"class":2139},[2129,12325,2761],{"class":2143},[2129,12327,12328,12330,12332,12334,12336,12338,12340,12342,12344,12346,12348,12350,12352,12354,12356],{"class":2131,"line":3612},[2129,12329,2198],{"class":2135},[2129,12331,2264],{"class":2135},[2129,12333,8370],{"class":2201},[2129,12335,2741],{"class":2143},[2129,12337,3049],{"class":2139},[2129,12339,2735],{"class":2143},[2129,12341,2792],{"class":2139},[2129,12343,2747],{"class":2143},[2129,12345,2511],{"class":2205},[2129,12347,2298],{"class":2139},[2129,12349,2144],{"class":2143},[2129,12351,8389],{"class":2188},[2129,12353,2217],{"class":2143},[2129,12355,6203],{"class":2201},[2129,12357,2256],{"class":2143},[2129,12359,12360,12362,12364,12366,12368,12370,12372],{"class":2131,"line":3627},[2129,12361,8400],{"class":2135},[2129,12363,8403],{"class":2201},[2129,12365,8406],{"class":2135},[2129,12367,2659],{"class":2422},[2129,12369,8411],{"class":2143},[2129,12371,6203],{"class":2201},[2129,12373,2905],{"class":2143},[2129,12375,12376,12378,12380,12382,12384,12386,12388,12390],{"class":2131,"line":3632},[2129,12377,8420],{"class":2201},[2129,12379,178],{"class":2143},[2129,12381,8425],{"class":2188},[2129,12383,2217],{"class":2143},[2129,12385,8293],{"class":2201},[2129,12387,178],{"class":2143},[2129,12389,8316],{"class":2135},[2129,12391,7784],{"class":2143},[2129,12393,12394],{"class":2131,"line":3637},[2129,12395,2980],{"class":2143},[2129,12397,12398],{"class":2131,"line":6959},[2129,12399,8444],{"class":2201},[2129,12401,12402],{"class":2131,"line":6965},[2129,12403,2353],{"class":2143},[2129,12405,12406],{"class":2131,"line":6971},[2129,12407,2179],{"emptyLinePlaceholder":1766},[2129,12409,12410,12412,12414,12416,12418,12420,12422,12424,12426,12428],{"class":2131,"line":6986},[2129,12411,2185],{"class":2135},[2129,12413,8459],{"class":2188},[2129,12415,2217],{"class":2143},[2129,12417,8293],{"class":2201},[2129,12419,2784],{"class":2143},[2129,12421,2321],{"class":2135},[2129,12423,8300],{"class":2139},[2129,12425,3106],{"class":2143},[2129,12427,7874],{"class":2139},[2129,12429,2905],{"class":2143},[2129,12431,12432,12434,12436,12438,12440,12442,12444,12446,12448,12450,12452,12454],{"class":2131,"line":7008},[2129,12433,2198],{"class":2135},[2129,12435,2264],{"class":2135},[2129,12437,8484],{"class":2201},[2129,12439,2206],{"class":2205},[2129,12441,8154],{"class":2139},[2129,12443,5940],{"class":2143},[2129,12445,8493],{"class":2201},[2129,12447,4472],{"class":2143},[2129,12449,2423],{"class":2422},[2129,12451,4477],{"class":2143},[2129,12453,6838],{"class":2422},[2129,12455,8504],{"class":2143},[2129,12457,12458,12460,12462,12464,12466,12468,12470],{"class":2131,"line":7040},[2129,12459,8400],{"class":2135},[2129,12461,6241],{"class":2201},[2129,12463,8406],{"class":2135},[2129,12465,2659],{"class":2422},[2129,12467,8411],{"class":2143},[2129,12469,6838],{"class":2422},[2129,12471,2905],{"class":2143},[2129,12473,12474,12476,12478,12480,12482,12484,12486,12488,12490],{"class":2131,"line":7058},[2129,12475,8525],{"class":2201},[2129,12477,8528],{"class":2143},[2129,12479,6250],{"class":2201},[2129,12481,2426],{"class":2143},[2129,12483,2511],{"class":2205},[2129,12485,8537],{"class":2201},[2129,12487,178],{"class":2143},[2129,12489,8316],{"class":2188},[2129,12491,2230],{"class":2143},[2129,12493,12494],{"class":2131,"line":7063},[2129,12495,2980],{"class":2143},[2129,12497,12498],{"class":2131,"line":7068},[2129,12499,8552],{"class":2201},[2129,12501,12502],{"class":2131,"line":7074},[2129,12503,2353],{"class":2143},[2129,12505,12506],{"class":2131,"line":7106},[2129,12507,2179],{"emptyLinePlaceholder":1766},[2129,12509,12510],{"class":2131,"line":7112},[2129,12511,12009],{"class":2143},[2129,12513,12514,12516,12518,12520],{"class":2131,"line":7123},[2129,12515,3691],{"class":2135},[2129,12517,3694],{"class":2135},[2129,12519,10482],{"class":2139},[2129,12521,2905],{"class":2143},[2129,12523,12524,12527,12529,12531,12533,12535],{"class":2131,"line":7129},[2129,12525,12526],{"class":2201},"    test_objects",[2129,12528,2741],{"class":2143},[2129,12530,3049],{"class":2139},[2129,12532,2735],{"class":2143},[2129,12534,8745],{"class":2139},[2129,12536,8231],{"class":2143},[2129,12538,12539],{"class":2131,"line":7151},[2129,12540,2353],{"class":2143},[2129,12542,12543],{"class":2131,"line":7156},[2129,12544,2179],{"emptyLinePlaceholder":1766},[2129,12546,12547],{"class":2131,"line":7192},[2129,12548,12009],{"class":2143},[2129,12550,12551,12553,12555],{"class":2131,"line":7231},[2129,12552,2732],{"class":2135},[2129,12554,10482],{"class":2139},[2129,12556,2905],{"class":2143},[2129,12558,12559],{"class":2131,"line":7236},[2129,12560,12561],{"class":2143},"    #[wasm_bindgen(constructor)]\n",[2129,12563,12564,12567,12569,12571,12573,12575],{"class":2131,"line":7242},[2129,12565,12566],{"class":2135},"    pub",[2129,12568,4863],{"class":2135},[2129,12570,3094],{"class":2188},[2129,12572,10580],{"class":2143},[2129,12574,3109],{"class":2139},[2129,12576,2905],{"class":2143},[2129,12578,12579,12581],{"class":2131,"line":7248},[2129,12580,3116],{"class":2139},[2129,12582,2905],{"class":2143},[2129,12584,12585,12588,12590,12592,12594,12596],{"class":2131,"line":7266},[2129,12586,12587],{"class":2201},"            test_objects",[2129,12589,2741],{"class":2143},[2129,12591,3049],{"class":2139},[2129,12593,2144],{"class":2143},[2129,12595,2248],{"class":2188},[2129,12597,6544],{"class":2143},[2129,12599,12600],{"class":2131,"line":7271},[2129,12601,2892],{"class":2143},[2129,12603,12604],{"class":2131,"line":7277},[2129,12605,2980],{"class":2143},[2129,12607,12608],{"class":2131,"line":7285},[2129,12609,2179],{"emptyLinePlaceholder":1766},[2129,12611,12612],{"class":2131,"line":7303},[2129,12613,12614],{"class":2143},"    #[wasm_bindgen]\n",[2129,12616,12617,12619,12621,12623,12625,12627,12629,12631,12633,12635,12637],{"class":2131,"line":7318},[2129,12618,12566],{"class":2135},[2129,12620,4863],{"class":2135},[2129,12622,10647],{"class":2188},[2129,12624,2318],{"class":2143},[2129,12626,2321],{"class":2135},[2129,12628,2776],{"class":2139},[2129,12630,931],{"class":2143},[2129,12632,8760],{"class":2201},[2129,12634,2741],{"class":2143},[2129,12636,10662],{"class":2139},[2129,12638,3177],{"class":2143},[2129,12640,12641,12643,12645,12647,12649,12651,12653,12655],{"class":2131,"line":7323},[2129,12642,2897],{"class":2135},[2129,12644,2264],{"class":2135},[2129,12646,8537],{"class":2201},[2129,12648,2206],{"class":2205},[2129,12650,7912],{"class":2139},[2129,12652,2144],{"class":2143},[2129,12654,8603],{"class":2188},[2129,12656,2230],{"class":2143},[2129,12658,12659],{"class":2131,"line":7346},[2129,12660,12661],{"class":2398},"        \u002F\u002F Get the current time in ms\n",[2129,12663,12664,12666,12669,12671,12673,12675,12677,12679,12681,12683,12685],{"class":2131,"line":7351},[2129,12665,2952],{"class":2139},[2129,12667,12668],{"class":2143},".test_objects ",[2129,12670,2511],{"class":2205},[2129,12672,2298],{"class":2139},[2129,12674,2144],{"class":2143},[2129,12676,8389],{"class":2188},[2129,12678,2217],{"class":2143},[2129,12680,8760],{"class":2201},[2129,12682,9768],{"class":2135},[2129,12684,11366],{"class":2139},[2129,12686,2256],{"class":2143},[2129,12688,12689],{"class":2131,"line":7357},[2129,12690,2179],{"emptyLinePlaceholder":1766},[2129,12692,12693,12696,12698,12700,12702,12704,12706],{"class":2131,"line":7380},[2129,12694,12695],{"class":2135},"        for",[2129,12697,8403],{"class":2201},[2129,12699,8406],{"class":2135},[2129,12701,2659],{"class":2422},[2129,12703,8411],{"class":2143},[2129,12705,8760],{"class":2201},[2129,12707,2905],{"class":2143},[2129,12709,12710,12712,12715,12717,12719,12721],{"class":2131,"line":7411},[2129,12711,3500],{"class":2139},[2129,12713,12714],{"class":2143},".test_objects.",[2129,12716,8425],{"class":2188},[2129,12718,2217],{"class":2143},[2129,12720,8745],{"class":2139},[2129,12722,2905],{"class":2143},[2129,12724,12725,12728,12730,12732,12734,12736,12738,12740,12742],{"class":2131,"line":7417},[2129,12726,12727],{"class":2201},"                path",[2129,12729,2741],{"class":2143},[2129,12731,8803],{"class":2188},[2129,12733,2318],{"class":2143},[2129,12735,2321],{"class":2135},[2129,12737,8537],{"class":2201},[2129,12739,931],{"class":2143},[2129,12741,6415],{"class":2422},[2129,12743,5463],{"class":2143},[2129,12745,12746,12749,12751,12753],{"class":2131,"line":7440},[2129,12747,12748],{"class":2201},"                stats",[2129,12750,2741],{"class":2143},[2129,12752,8241],{"class":2139},[2129,12754,2905],{"class":2143},[2129,12756,12757,12760,12762,12764,12766,12768,12770],{"class":2131,"line":7473},[2129,12758,12759],{"class":2201},"                    owner_id",[2129,12761,2741],{"class":2143},[2129,12763,8836],{"class":2188},[2129,12765,2318],{"class":2143},[2129,12767,2321],{"class":2135},[2129,12769,8537],{"class":2201},[2129,12771,5463],{"class":2143},[2129,12773,12774,12777,12779,12781,12783,12785,12787],{"class":2131,"line":7484},[2129,12775,12776],{"class":2201},"                    group_id",[2129,12778,2741],{"class":2143},[2129,12780,8836],{"class":2188},[2129,12782,2318],{"class":2143},[2129,12784,2321],{"class":2135},[2129,12786,8537],{"class":2201},[2129,12788,5463],{"class":2143},[2129,12790,12791,12794,12796,12798,12800,12802,12804],{"class":2131,"line":7492},[2129,12792,12793],{"class":2201},"                    size",[2129,12795,2741],{"class":2143},[2129,12797,8836],{"class":2188},[2129,12799,2318],{"class":2143},[2129,12801,2321],{"class":2135},[2129,12803,8537],{"class":2201},[2129,12805,5463],{"class":2143},[2129,12807,12808,12811,12813,12815,12817,12819,12821],{"class":2131,"line":7517},[2129,12809,12810],{"class":2201},"                    compressed_size",[2129,12812,2741],{"class":2143},[2129,12814,8836],{"class":2188},[2129,12816,2318],{"class":2143},[2129,12818,2321],{"class":2135},[2129,12820,8537],{"class":2201},[2129,12822,5463],{"class":2143},[2129,12824,12825,12828,12830,12832,12834,12836,12838],{"class":2131,"line":7523},[2129,12826,12827],{"class":2201},"                    last_read",[2129,12829,2741],{"class":2143},[2129,12831,8836],{"class":2188},[2129,12833,2318],{"class":2143},[2129,12835,2321],{"class":2135},[2129,12837,8537],{"class":2201},[2129,12839,5463],{"class":2143},[2129,12841,12842,12845,12847,12849,12851,12853,12855],{"class":2131,"line":7528},[2129,12843,12844],{"class":2201},"                    last_modified",[2129,12846,2741],{"class":2143},[2129,12848,8836],{"class":2188},[2129,12850,2318],{"class":2143},[2129,12852,2321],{"class":2135},[2129,12854,8537],{"class":2201},[2129,12856,5463],{"class":2143},[2129,12858,12859,12862,12864,12866,12868,12870,12872],{"class":2131,"line":7533},[2129,12860,12861],{"class":2201},"                    created",[2129,12863,2741],{"class":2143},[2129,12865,8836],{"class":2188},[2129,12867,2318],{"class":2143},[2129,12869,2321],{"class":2135},[2129,12871,8537],{"class":2201},[2129,12873,5463],{"class":2143},[2129,12875,12876,12879,12881,12883,12885,12887,12889],{"class":2131,"line":7552},[2129,12877,12878],{"class":2201},"                    mode",[2129,12880,2741],{"class":2143},[2129,12882,8836],{"class":2188},[2129,12884,2318],{"class":2143},[2129,12886,2321],{"class":2135},[2129,12888,8537],{"class":2201},[2129,12890,5463],{"class":2143},[2129,12892,12893,12896,12898,12900,12902,12904,12906],{"class":2131,"line":7574},[2129,12894,12895],{"class":2201},"                    dev",[2129,12897,2741],{"class":2143},[2129,12899,8836],{"class":2188},[2129,12901,2318],{"class":2143},[2129,12903,2321],{"class":2135},[2129,12905,8537],{"class":2201},[2129,12907,5463],{"class":2143},[2129,12909,12910,12913,12915,12917,12919,12921,12923],{"class":2131,"line":7579},[2129,12911,12912],{"class":2201},"                    rdev",[2129,12914,2741],{"class":2143},[2129,12916,8836],{"class":2188},[2129,12918,2318],{"class":2143},[2129,12920,2321],{"class":2135},[2129,12922,8537],{"class":2201},[2129,12924,5463],{"class":2143},[2129,12926,12927,12930,12932,12934,12936,12938,12940],{"class":2131,"line":9121},[2129,12928,12929],{"class":2201},"                    ino",[2129,12931,2741],{"class":2143},[2129,12933,8836],{"class":2188},[2129,12935,2318],{"class":2143},[2129,12937,2321],{"class":2135},[2129,12939,8537],{"class":2201},[2129,12941,5463],{"class":2143},[2129,12943,12944,12947,12949,12951,12953,12955,12957],{"class":2131,"line":9126},[2129,12945,12946],{"class":2201},"                    nlink",[2129,12948,2741],{"class":2143},[2129,12950,8836],{"class":2188},[2129,12952,2318],{"class":2143},[2129,12954,2321],{"class":2135},[2129,12956,8537],{"class":2201},[2129,12958,5463],{"class":2143},[2129,12960,12961],{"class":2131,"line":9152},[2129,12962,12963],{"class":2143},"                },\n",[2129,12965,12966,12969,12971,12973],{"class":2131,"line":9173},[2129,12967,12968],{"class":2201},"                chunks",[2129,12970,2741],{"class":2143},[2129,12972,9046],{"class":2188},[2129,12974,9049],{"class":2143},[2129,12976,12977,12980,12982,12984,12986],{"class":2131,"line":9182},[2129,12978,12979],{"class":2188},"                    generate_random_sha256",[2129,12981,2318],{"class":2143},[2129,12983,2321],{"class":2135},[2129,12985,8537],{"class":2201},[2129,12987,5463],{"class":2143},[2129,12989,12990,12992,12994,12996,12998],{"class":2131,"line":9191},[2129,12991,12979],{"class":2188},[2129,12993,2318],{"class":2143},[2129,12995,2321],{"class":2135},[2129,12997,8537],{"class":2201},[2129,12999,5463],{"class":2143},[2129,13001,13002,13004,13006,13008,13010],{"class":2131,"line":9197},[2129,13003,12979],{"class":2188},[2129,13005,2318],{"class":2143},[2129,13007,2321],{"class":2135},[2129,13009,8537],{"class":2201},[2129,13011,5463],{"class":2143},[2129,13013,13014],{"class":2131,"line":9219},[2129,13015,13016],{"class":2143},"                ],\n",[2129,13018,13019,13022,13024,13026,13028,13030,13032],{"class":2131,"line":9224},[2129,13020,13021],{"class":2201},"                sha256",[2129,13023,2741],{"class":2143},[2129,13025,9101],{"class":2188},[2129,13027,2318],{"class":2143},[2129,13029,2321],{"class":2135},[2129,13031,8537],{"class":2201},[2129,13033,5463],{"class":2143},[2129,13035,13036],{"class":2131,"line":9229},[2129,13037,13038],{"class":2143},"            });\n",[2129,13040,13041],{"class":2131,"line":9251},[2129,13042,2892],{"class":2143},[2129,13044,13045],{"class":2131,"line":9257},[2129,13046,2980],{"class":2143},[2129,13048,13049],{"class":2131,"line":9264},[2129,13050,2179],{"emptyLinePlaceholder":1766},[2129,13052,13053,13056,13058],{"class":2131,"line":9272},[2129,13054,13055],{"class":2143},"    #[wasm_bindgen(js_name ",[2129,13057,2511],{"class":2205},[2129,13059,13060],{"class":2143}," toString)]\n",[2129,13062,13063,13065,13067,13069,13071,13073,13075,13077],{"class":2131,"line":9297},[2129,13064,12566],{"class":2135},[2129,13066,4863],{"class":2135},[2129,13068,11411],{"class":2188},[2129,13070,2318],{"class":2143},[2129,13072,3533],{"class":2139},[2129,13074,3106],{"class":2143},[2129,13076,11420],{"class":2139},[2129,13078,2905],{"class":2143},[2129,13080,13081,13084],{"class":2131,"line":9303},[2129,13082,13083],{"class":2188},"        format!",[2129,13085,4631],{"class":2143},[2129,13087,13088,13091],{"class":2131,"line":9309},[2129,13089,13090],{"class":2220},"            \"TestObjectWrapper {{ test_objects: {} }}\"",[2129,13092,3022],{"class":2143},[2129,13094,13095,13097,13099,13101],{"class":2131,"line":9316},[2129,13096,3500],{"class":2139},[2129,13098,12714],{"class":2143},[2129,13100,2853],{"class":2188},[2129,13102,5066],{"class":2143},[2129,13104,13105],{"class":2131,"line":9324},[2129,13106,13107],{"class":2143},"        )\n",[2129,13109,13110],{"class":2131,"line":9349},[2129,13111,2980],{"class":2143},[2129,13113,13114],{"class":2131,"line":9354},[2129,13115,2353],{"class":2143},[11,13117,13118,13119,178],{},"Pour utiliser notre module dans Node.JS, nous devons importer le module ",[183,13120,13121],{},"wasm-bindgen",[2122,13123,13125],{"className":6105,"code":13124,"language":6107,"meta":1645,"style":1645},"const { webcrypto } = require('node:crypto')\nglobal.crypto = webcrypto\n\nconst wasm = require('..\u002Fpkg\u002Ftest_rust_was.js');\nconst filesize = require(\"filesize.js\");\n\nglobal.gc();\nconst memoryBefore = process.memoryUsage().rss;\n\nconst time = Date.now();\n\nconst nbObjects = 1300000;\nconst testArray = new wasm.TestObjectWrapper();\ntestArray.fill(nbObjects);\n\nconsole.log(\"Creation time: \", Date.now() - time);\n\nglobal.gc();\nconst memoryAfter = process.memoryUsage().rss;\n\nconsole.log(\"Memory consumption in JS: \", filesize.default(memoryAfter - memoryBefore));\nconsole.log(\"Memory consumption calculated by head: \", filesize.default(432));\nconsole.log(\"Memory consumption by objects: \", filesize.default((memoryAfter - memoryBefore) \u002F nbObjects));\n  \nconsole.log(testArray.toString());\n",[183,13126,13127,13149,13163,13167,13185,13201,13205,13215,13236,13240,13256,13260,13273,13291,13305,13309,13337,13341,13351,13371,13375,13406,13434,13470,13475],{"__ignoreMap":1645},[2129,13128,13129,13131,13133,13136,13138,13140,13142,13144,13147],{"class":2131,"line":2132},[2129,13130,6114],{"class":2135},[2129,13132,5940],{"class":2143},[2129,13134,13135],{"class":2139},"webcrypto",[2129,13137,6169],{"class":2143},[2129,13139,2511],{"class":2205},[2129,13141,6122],{"class":2188},[2129,13143,2217],{"class":2143},[2129,13145,13146],{"class":2220},"'node:crypto'",[2129,13148,2975],{"class":2143},[2129,13150,13151,13153,13155,13158,13160],{"class":2131,"line":1646},[2129,13152,6892],{"class":2139},[2129,13154,178],{"class":2143},[2129,13156,13157],{"class":2201},"crypto",[2129,13159,2206],{"class":2205},[2129,13161,13162],{"class":2201}," webcrypto\n",[2129,13164,13165],{"class":2131,"line":1651},[2129,13166,2179],{"emptyLinePlaceholder":1766},[2129,13168,13169,13171,13174,13176,13178,13180,13183],{"class":2131,"line":2182},[2129,13170,6114],{"class":2135},[2129,13172,13173],{"class":2139}," wasm",[2129,13175,2206],{"class":2205},[2129,13177,6122],{"class":2188},[2129,13179,2217],{"class":2143},[2129,13181,13182],{"class":2220},"'..\u002Fpkg\u002Ftest_rust_was.js'",[2129,13184,2256],{"class":2143},[2129,13186,13187,13189,13191,13193,13195,13197,13199],{"class":2131,"line":2195},[2129,13188,6114],{"class":2135},[2129,13190,6117],{"class":2139},[2129,13192,2206],{"class":2205},[2129,13194,6122],{"class":2188},[2129,13196,2217],{"class":2143},[2129,13198,6127],{"class":2220},[2129,13200,2256],{"class":2143},[2129,13202,13203],{"class":2131,"line":2233},[2129,13204,2179],{"emptyLinePlaceholder":1766},[2129,13206,13207,13209,13211,13213],{"class":2131,"line":2259},[2129,13208,6892],{"class":2139},[2129,13210,178],{"class":2143},[2129,13212,6897],{"class":2188},[2129,13214,2230],{"class":2143},[2129,13216,13217,13219,13221,13223,13225,13227,13229,13231,13234],{"class":2131,"line":2286},[2129,13218,6114],{"class":2135},[2129,13220,6911],{"class":2139},[2129,13222,2206],{"class":2205},[2129,13224,6916],{"class":2139},[2129,13226,178],{"class":2143},[2129,13228,6921],{"class":2188},[2129,13230,3541],{"class":2143},[2129,13232,13233],{"class":2201},"rss",[2129,13235,2155],{"class":2143},[2129,13237,13238],{"class":2131,"line":2307},[2129,13239,2179],{"emptyLinePlaceholder":1766},[2129,13241,13242,13244,13246,13248,13250,13252,13254],{"class":2131,"line":2332},[2129,13243,6114],{"class":2135},[2129,13245,6940],{"class":2139},[2129,13247,2206],{"class":2205},[2129,13249,6945],{"class":2139},[2129,13251,178],{"class":2143},[2129,13253,6950],{"class":2188},[2129,13255,2230],{"class":2143},[2129,13257,13258],{"class":2131,"line":2350},[2129,13259,2179],{"emptyLinePlaceholder":1766},[2129,13261,13262,13264,13266,13268,13271],{"class":2131,"line":2564},[2129,13263,6114],{"class":2135},[2129,13265,6976],{"class":2139},[2129,13267,2206],{"class":2205},[2129,13269,13270],{"class":2422}," 1300000",[2129,13272,2155],{"class":2143},[2129,13274,13275,13277,13279,13281,13283,13285,13287,13289],{"class":2131,"line":2570},[2129,13276,6114],{"class":2135},[2129,13278,6991],{"class":2139},[2129,13280,2206],{"class":2205},[2129,13282,3094],{"class":2135},[2129,13284,13173],{"class":2139},[2129,13286,178],{"class":2143},[2129,13288,10540],{"class":2188},[2129,13290,2230],{"class":2143},[2129,13292,13293,13295,13297,13299,13301,13303],{"class":2131,"line":2576},[2129,13294,11654],{"class":2139},[2129,13296,178],{"class":2143},[2129,13298,11659],{"class":2188},[2129,13300,2217],{"class":2143},[2129,13302,7003],{"class":2201},[2129,13304,2256],{"class":2143},[2129,13306,13307],{"class":2131,"line":2582},[2129,13308,2179],{"emptyLinePlaceholder":1766},[2129,13310,13311,13313,13315,13317,13319,13321,13323,13325,13327,13329,13331,13333,13335],{"class":2131,"line":2587},[2129,13312,7077],{"class":2139},[2129,13314,178],{"class":2143},[2129,13316,7082],{"class":2188},[2129,13318,2217],{"class":2143},[2129,13320,7087],{"class":2220},[2129,13322,931],{"class":2143},[2129,13324,35],{"class":2139},[2129,13326,178],{"class":2143},[2129,13328,6950],{"class":2188},[2129,13330,2856],{"class":2143},[2129,13332,2627],{"class":2205},[2129,13334,6940],{"class":2201},[2129,13336,2256],{"class":2143},[2129,13338,13339],{"class":2131,"line":2604},[2129,13340,2179],{"emptyLinePlaceholder":1766},[2129,13342,13343,13345,13347,13349],{"class":2131,"line":2610},[2129,13344,6892],{"class":2139},[2129,13346,178],{"class":2143},[2129,13348,6897],{"class":2188},[2129,13350,2230],{"class":2143},[2129,13352,13353,13355,13357,13359,13361,13363,13365,13367,13369],{"class":2131,"line":2644},[2129,13354,6114],{"class":2135},[2129,13356,7134],{"class":2139},[2129,13358,2206],{"class":2205},[2129,13360,6916],{"class":2139},[2129,13362,178],{"class":2143},[2129,13364,6921],{"class":2188},[2129,13366,3541],{"class":2143},[2129,13368,13233],{"class":2201},[2129,13370,2155],{"class":2143},[2129,13372,13373],{"class":2131,"line":2664},[2129,13374,2179],{"emptyLinePlaceholder":1766},[2129,13376,13377,13379,13381,13383,13385,13388,13390,13392,13394,13396,13398,13400,13402,13404],{"class":2131,"line":3216},[2129,13378,7077],{"class":2139},[2129,13380,178],{"class":2143},[2129,13382,7082],{"class":2188},[2129,13384,2217],{"class":2143},[2129,13386,13387],{"class":2220},"\"Memory consumption in JS: \"",[2129,13389,931],{"class":2143},[2129,13391,7172],{"class":2139},[2129,13393,178],{"class":2143},[2129,13395,7177],{"class":2188},[2129,13397,2217],{"class":2143},[2129,13399,7182],{"class":2201},[2129,13401,7185],{"class":2205},[2129,13403,6911],{"class":2201},[2129,13405,4135],{"class":2143},[2129,13407,13408,13410,13412,13414,13416,13419,13421,13423,13425,13427,13429,13432],{"class":2131,"line":1776},[2129,13409,7077],{"class":2139},[2129,13411,178],{"class":2143},[2129,13413,7082],{"class":2188},[2129,13415,2217],{"class":2143},[2129,13417,13418],{"class":2220},"\"Memory consumption calculated by head: \"",[2129,13420,931],{"class":2143},[2129,13422,7172],{"class":2139},[2129,13424,178],{"class":2143},[2129,13426,7177],{"class":2188},[2129,13428,2217],{"class":2143},[2129,13430,13431],{"class":2422},"432",[2129,13433,4135],{"class":2143},[2129,13435,13436,13438,13440,13442,13444,13446,13448,13450,13452,13454,13456,13458,13460,13462,13464,13466,13468],{"class":2131,"line":3248},[2129,13437,7077],{"class":2139},[2129,13439,178],{"class":2143},[2129,13441,7082],{"class":2188},[2129,13443,2217],{"class":2143},[2129,13445,7203],{"class":2220},[2129,13447,931],{"class":2143},[2129,13449,7172],{"class":2139},[2129,13451,178],{"class":2143},[2129,13453,7177],{"class":2188},[2129,13455,7214],{"class":2143},[2129,13457,7182],{"class":2201},[2129,13459,7185],{"class":2205},[2129,13461,6911],{"class":2201},[2129,13463,4026],{"class":2143},[2129,13465,4846],{"class":2205},[2129,13467,6976],{"class":2201},[2129,13469,4135],{"class":2143},[2129,13471,13472],{"class":2131,"line":3278},[2129,13473,13474],{"class":2143},"  \n",[2129,13476,13477,13479,13481,13483,13485,13487,13489,13491],{"class":2131,"line":3293},[2129,13478,7077],{"class":2139},[2129,13480,178],{"class":2143},[2129,13482,7082],{"class":2188},[2129,13484,2217],{"class":2143},[2129,13486,11654],{"class":2139},[2129,13488,178],{"class":2143},[2129,13490,11805],{"class":2188},[2129,13492,7784],{"class":2143},[11,13494,13495],{},"Nous n'oublions pas de compiler notre module en mode release et avec la target nodejs.",[11,13497,13498],{},"Le résultat est alors le suivant :",[21,13500,13501,13509],{},[24,13502,13503],{},[27,13504,13505,13507],{},[30,13506],{},[30,13508],{},[40,13510,13511,13517,13524,13531],{},[27,13512,13513,13515],{},[45,13514,7611],{},[45,13516,7614],{},[27,13518,13519,13521],{},[45,13520,7619],{},[45,13522,13523],{},"42 secondes",[27,13525,13526,13528],{},[45,13527,7627],{},[45,13529,13530],{},"450,6 Mo",[27,13532,13533,13535],{},[45,13534,7635],{},[45,13536,13537],{},"363.5 octets",[11,13539,13540],{},"Wasm permet d'améliorer la consommation mémoire. Sur mon exemple, le temps de création n'est pas très impressionnant\nmais est, je crois, lié à la génération des nombres aléatoires.",[11,13542,13543],{},"L'utilisation de WASM pourrait être une bonne alternative.",[11,13545,13546],{},"Quand utiliser WASM et quand faire un module node ? L'avantage de faire du Wasm, c'est que le module peut tourner\négalement côté navigateur. L'autre avantage est que le module n'a pas besoin d'être recompilé en cas de changement de\nplateforme (Linux, Windows, ... ; x86, ARM, ...).",[11,13548,13549],{},"D'un autre côté, un module node natif a l'avantage d'être plus simple (moins de bindings) et sera sûrement plus rapide\nà l'exécution.",[127,13551,1620],{"id":1619},[11,13553,13554],{},"Pour améliorer les performances de notre application, nous allons donc écrire un module natif en Rust avec N-API.",[11,13556,13557,13558,178],{},"Vous pouvez retrouver le code source de cet article sur mon dépôt ",[48,13559,13562],{"href":13560,"rel":13561},"https:\u002F\u002Fgogs.shadoware.org\u002FShadowareOrg\u002Fblog_compare_js_rust_wasm",[346],"Gitea",[6033,13564,13565],{},"html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}html pre.shiki code .s_ZVi, html code.shiki .s_ZVi{--shiki-default:#E06C75;--shiki-default-font-style:italic}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":1645,"searchDepth":1646,"depth":1646,"links":13567},[13568,13569,13570,13571,13572,13573,13575],{"id":6088,"depth":1646,"text":6089},{"id":6098,"depth":1646,"text":6099},{"id":7671,"depth":1646,"text":7672},{"id":7860,"depth":1646,"text":7861},{"id":9861,"depth":1646,"text":9862},{"id":11854,"depth":1646,"text":13574},"Conclusion - Wasm",{"id":1619,"depth":1646,"text":1620},{"type":8,"value":13577},[13578,13580,13582],[127,13579,6089],{"id":6088},[11,13581,6092],{},[11,13583,6095],{},{"planet":1766},{"title":6083,"description":1645},"woodstock_rust","posts\u002FWoodstock\u002F2023-05-10_woodstock_rust",[1682,1772,1773,6078,6079],"RXnKX2dJmrksM5sZOeN70MXCyeSAMu1H3Jj5D3fie28",{"id":13591,"title":13592,"author":6,"body":13593,"category":6054,"categorySlug":6055,"date":15699,"description":1645,"excerpt":15700,"extension":1763,"location":1764,"meta":15719,"navigation":1766,"path":78,"published":1766,"seo":15720,"slug":15721,"stem":15722,"tags":15723,"timeToRead":3248,"__hash__":15724},"posts\u002Fposts\u002FWoodstock\u002F2021-04-18_woodstock_protocol_language_sauvegarde.md","Woodstock Backup - Protocol et Language de sauvegarde",{"type":8,"value":13594,"toc":15685},[13595,13602,13613,13620,13623,13626,13629,13632,13635,13638,13642,13645,13659,13662,13670,13673,13676,13692,13696,13699,13713,13719,13722,13725,13742,13745,13756,13762,13765,13768,13772,13778,13781,13805,13808,13811,13814,13817,13820,13824,13827,13832,13835,13846,13849,13853,13856,13859,13878,13881,13889,13892,13898,13922,13925,13929,13932,13952,13955,13958,13961,13964,13968,13971,13974,13977,13980,13984,14003,14029,14035,14049,14062,14077,14087,14307,14314,15311,15319,15328,15331,15342,15348,15352,15364,15373,15376,15393,15396,15399,15479,15482,15485,15488,15546,15558,15561,15591,15594,15597,15602,15606,15629,15633,15636,15639,15642,15645,15662,15665,15668,15671,15682],[11,13596,13597],{},[120,13598],{"alt":13592,"src":13599,"className":13600,"width":13601},"\u002FWoodstock\u002Fsplash_proto.png",[125],"400px,",[1990,13603,13604,13607,13610],{},[11,13605,13606],{},"Note de 2023 : Ce billet a été écrit en avril 2021, il y a deux ans, mais n'a jamais été publié. Le temps passe vite.",[11,13608,13609],{},"Depuis lors, j'ai travaillé sur d'autres projets, mais aussi sur ce logiciel de sauvegarde. En progressant dans le\ndéveloppement du projet, j'ai pu optimiser les performances et me faire une opinion sur le choix que j'ai finalement\nfait, que je partagerai à la fin de l'article.",[11,13611,13612],{},"Je mettrai à jour mes conclusions en fonction de mes avancées sur le sujet.",[11,13614,13615,13616,13619],{},"Dans notre ",[48,13617,13618],{"href":64},"précédent article",", nous avons vu comment dédupliquer les fichiers dans un pool sans\nutiliser btrfs, un système de fichier permettant la déduplication. Pour pouvoir copier les fichiers dans notre pool,\nle logiciel doit savoir comment écrire les fichiers de manière appropriée.",[11,13621,13622],{},"Si nous voulons continuer à utiliser rsync, nous pourrions envisager de créer un système de fichiers FUSE (Filesystem in\nUser Space) pour faire le pont entre rsync et notre pool de stockage. Cela nous permettrait de continuer à utiliser\nrsync pour les sauvegardes et les restaurations. Cependant, cela nécessiterait la mise en œuvre d'un système de fichiers\ncomplet, y compris la lecture, l'écriture, la déduplication, etc., pour un usage très spécifique de rsync. En fin de\ncompte, le système de fichiers ne serait utilisé que pour la sauvegarde (ajout de nouveaux éléments) et la restauration\n(lecture d'une sauvegarde). Il n'y aurait pas d'écriture aléatoire dans le système de fichiers, ni dans un fichier même.",[11,13624,13625],{},"Après avoir rapidement examiné cette possibilité, je l'ai écartée car elle me semble être une solution trop lourde pour\nmes besoins.",[11,13627,13628],{},"Par conséquent, nous devrons nous passer de rsync. Je n'ai pas trouvé de bibliothèque permettant d'implémenter\nfacilement une synchronisation sur le protocole rsync. Nous devrons donc écrire notre propre protocole de\nsynchronisation de fichiers.",[11,13630,13631],{},"Lorsque nous développerons ce protocole, nous devrons nous assurer de la sécurité. En effet, rsync est capable de\nsynchroniser des fichiers via un tunnel ssh. Notre protocole devra empêcher les attaquants d'écouter ce qui est\nsynchronisé et d'accéder aux fichiers sans autorisation.",[11,13633,13634],{},"Dans la suite de cet article, nous étudierons comment écrire notre protocole de synchronisation et nous comparerons les\nperformances de sauvegarde entre le langage JavaScript (actuellement utilisé par le serveur de sauvegarde) et le\nlangage C++.",[11,13636,13637],{},"Comme toujours, n'hésitez pas à me faire part de vos commentaires et de vos avis (système de commentaires en bas de la\npage).",[127,13639,13641],{"id":13640},"création-de-la-communication-périphériqueserveur","Création de la communication périphérique\u002Fserveur",[11,13643,13644],{},"Nous allons aborder la question de la communication entre le client et le serveur. Afin d'éviter toute confusion entre\nle client et le serveur dans le sens réseau et le client et le serveur dans le sens applicatif, nous allons désigner :",[505,13646,13647,13653],{},[369,13648,13649,13652],{},[183,13650,13651],{},"Périphérique"," pour le client à sauvegarder",[369,13654,13655,13658],{},[183,13656,13657],{},"Serveur de sauvegarde"," pour le serveur de sauvegarde.",[11,13660,13661],{},"En ce qui concerne le protocole de communication, je pense utiliser une HTTP2 plutôt que d'ouvrir un socket TCP et\ntravailler directement dessus. L'utilisation d'un protocole de haut niveau me permettra de m'appuyer sur des\nbibliothèques existantes et déjà largement utilisées. De plus, je n'aurai pas à gérer :",[505,13663,13664,13667],{},[369,13665,13666],{},"la compression",[369,13668,13669],{},"la prise en charge de TLS",[11,13671,13672],{},"En raison de la nature d'HTTP2, l'initiateur de la connexion est important : il faut déterminer qui est le serveur HTTP\net qui est le client HTTP.",[11,13674,13675],{},"Dans la suite de cet article, nous allons essayer de répondre aux deux questions suivantes :",[505,13677,13678,13681],{},[369,13679,13680],{},"Comment effectuer la communication entre le serveur de sauvegarde et le périphérique à sauvegarder ?",[369,13682,13683,13684],{},"Qui doit initier la connexion ?\n",[505,13685,13686,13689],{},[369,13687,13688],{},"Est-ce que le serveur de sauvegarde doit contacter le périphérique ?",[369,13690,13691],{},"Ou est-ce que le périphérique doit contacter le serveur de sauvegarde ?",[132,13693,13695],{"id":13694},"le-serveur-de-sauvegarde-initie-la-connection","Le serveur de sauvegarde initie la connection",[11,13697,13698],{},"Nous allons parler de deux éléments clés pour la sauvegarde: le manifeste et les chunks.",[505,13700,13701,13707],{},[369,13702,13703,13706],{},[183,13704,13705],{},"Manifeste",": la liste des fichiers à sauvegarder avec le nom du fichier, les attributs, les acl, ... un hash du\nfichier et un hash des différents morceaux qui le constituent.",[369,13708,13709,13712],{},[183,13710,13711],{},"Chunk",": un morceau de fichier (un fichier peut être découpé en plusieurs morceaux de taille donnée).",[238,13714,13715],{},[120,13716],{"alt":13717,"src":13718},"Le serveur initie la connection","https:\u002F\u002Fwww.plantuml.com\u002Fplantuml\u002Fpng\u002FZP1DJWCn38NtEOKvm5oWYweIOe740i49hEILM2JErFcvqvnZBeO6LAcqAcBZZTydVtw7sjXQpyazj8WCojnWmiwzmmQCfZszhel97BTvwjZHiqeJJbAvIL4AeCHKkGzyi0NyXRwmUcHekwNODndSSCMuLCfCS-b6FlAfWuxYey2g-nsaQThJp-KTFUaeGW6LCgiSKLibxbItTRTBmpFnMFBCnbAB4W_upIx0L62urBp_szkw-3wlYvrhgUHZryz_Ydvd7JGu5t2lZ0Cqz9o-0000",[11,13720,13721],{},"Lorsqu'une sauvegarde doit démarrer, le serveur contacte le périphérique et initie la sauvegarde. Le périphérique\nenvoie ensuite les différents fichiers au serveur qui peut alors demander au périphérique les morceaux de fichiers\nqui lui manquent.",[11,13723,13724],{},"Voici des exemples de logiciels de sauvegarde où le périphérique initie la connexion:",[505,13726,13727,13734],{},[369,13728,13729,13733],{},[48,13730,1012],{"href":13731,"rel":13732},"https:\u002F\u002Fbackuppc.github.io\u002Fbackuppc\u002F",[346]," - basé sur un rsync modifié pour gérer la partie pool",[369,13735,13736,13741],{},[48,13737,13740],{"href":13738,"rel":13739},"https:\u002F\u002Fwww.urbackup.org\u002F",[346],"UrBackup"," - basé sur son propre client de sauvegarde à installer sur le périphérique",[11,13743,13744],{},"Lorsque le serveur initie la connexion sur le périphérique:",[505,13746,13747,13750,13753],{},[369,13748,13749],{},"Le serveur décide quand faire la sauvegarde (en fonction de quand la dernière sauvegarde a été construite, des heures\nde travail, ...).",[369,13751,13752],{},"Le serveur a besoin de pouvoir accéder aux machines, mais les périphériques n'ont pas besoin d'avoir un accès direct\nau serveur de sauvegarde. Cela permet de ne pas compromettre le serveur de sauvegarde en cas de compromission d'un\npériphérique.",[369,13754,13755],{},"Le serveur doit détecter la présence du périphérique (si ce dernier n'est pas toujours sur le réseau) et donc faire\nun ping régulier, par exemple.",[11,13757,13758,13759,13761],{},"Il faudra prévoir un ",[183,13760,318],{}," pour vérifier la présence des machines sur le réseau. Le serveur doit rester éveillé\n(et donc être actif) et ne peut pas juste attendre l'arrivée d'un périphérique.",[11,13763,13764],{},"Il doit y avoir un serveur (le serveur ne peut pas être juste un espace de stockage, comme un S3). Le serveur décide à\npartir du nouveau manifeste du client quels sont les fichiers qui ont changé et demande au client de lui envoyer ceux\nqui lui manquent.",[11,13766,13767],{},"Le serveur décide d'organiser son pool de sauvegarde comme il le souhaite, ainsi que le nombre de sauvegardes en\nparallèle. Il peut bloquer les sauvegardes si le pool est plein.",[132,13769,13771],{"id":13770},"le-périphérique-initie-la-connection","Le périphérique initie la connection",[238,13773,13774],{},[120,13775],{"alt":13776,"src":13777},"Le périphérique initie la connexion","https:\u002F\u002Fwww.plantuml.com\u002Fplantuml\u002Fpng\u002FZP3DJGD138NlKuKfW0PoG94G1sv8q80ryvArD3yRZxrAIMmTsnWQ52c00ENYW-tt_3tlkx6QbnpkR4815JQeS0WlsweoJwEU77J_GA_G1RgZvoecdAM3CbDdNt1aJGY1eyd2XijgoAtDD3TNYXCFbuF4IQ3z5_VldHqzjjfDFPgqIicfS9K3klq3zhQjULlZn7f4GRKXIz0gqAlyfjDbPcNfhH8lY2Fcfy_shlWQ-6-KfxeqeOHEfMa4-vdIDVwWEJbNwxTPNDWxiLCVOzU0ca98-FdoFMhoaZy0",[11,13779,13780],{},"Voici quelques exemples de logiciels de sauvegarde qui peuvent être déclenchés depuis le périphérique:",[505,13782,13783,13791,13797],{},[369,13784,13785,13790],{},[48,13786,13789],{"href":13787,"rel":13788},"https:\u002F\u002Fborgbackup.readthedocs.io\u002Fen\u002Fstable\u002Findex.html",[346],"Borg"," (avec un lieu de stockage)",[369,13792,13793,13790],{},[48,13794,1006],{"href":13795,"rel":13796},"https:\u002F\u002Frestic.net\u002F",[346],[369,13798,13799,13804],{},[48,13800,13803],{"href":13801,"rel":13802},"https:\u002F\u002Fburp.grke.org\u002F",[346],"Burp"," (avec un serveur qui centralise les sauvegardes)",[11,13806,13807],{},"Le périphérique sait quand il est allumé et connecté au réseau. S'il y a une erreur de sauvegarde (par exemple, si le\nserveur de stockage n'est pas disponible), il peut réessayer plus tard. Il n'est donc pas nécessaire de planifier des\nsauvegardes sur le serveur, mais il est nécessaire d'avoir un plan de sauvegarde pour chaque périphérique.",[11,13809,13810],{},"Le périphérique doit être capable de se connecter au serveur, donc il est important d'ouvrir le flux de données du\npériphérique vers le serveur (par exemple, si le périphérique est un ordinateur hors réseau interne). Cela peut être\nrésolu en créant un VPN.",[11,13812,13813],{},"Lorsque la sauvegarde est initialisée, le serveur peut renvoyer des informations au périphérique pour indiquer qu'il\nrefuse la sauvegarde (pool plein, ...). Le périphérique devra alors réessayer plus tard.",[11,13815,13816],{},"Si un périphérique est compromis, toutes les sauvegardes de ce périphérique sont en danger (il existe des solutions\npour bloquer les sauvegardes en lecture\u002Fécriture). Si le pool de sauvegarde  est mutualisé entre plusieurs machines,\ncela peut compromettre l'ensemble du pool.",[11,13818,13819],{},"Le serveur reçoit un manifeste et une liste de chunks. Si nous souhaitons mutualiser le pool de sauvegarde, nous ne\npouvons pas laisser le client accéder à l'intégralité du pool. Nous devons donc disposer d'un serveur qui gère les\nchunks et vérifie que les fichiers reçus correspondent bien aux sauvegardes effectuées. Il est important de s'assurer\nque les clients n'envoient pas de chunks qui ne sont pas relatifs à une sauvegarde ou qui ont un sha incorrect pour\néviter que les fichiers soient mal rangés. Il faut aussi bloquer la modification des anciennes sauvegardes, et aussi\nconditionner la lecture de ces derniers.",[132,13821,13823],{"id":13822},"choix","Choix",[11,13825,13826],{},"Personnellement, j'ai une préférence pour l'initialisation de la sauvegarde par le serveur. Dans mon projet actuel,\nj'ai déjà prévu le code pour gérer la file d'attente, lancer rsync, ainsi que le planificateur. Il serait facile de\nremplacer ce code par celui de mon nouveau serveur.",[1990,13828,13829],{},[11,13830,13831],{},"Note de 2023: En avançant dans la réécriture, les dépendances ont été mises à jour et une grande partie du code a\nété modifié. Par conséquent, remplacer l'appel à rsync par un appel au nouveau système de sauvegarde n'a pas été\naussi simple que prévu.",[11,13833,13834],{},"Il y a plusieurs raisons pour lesquelles je penche vers le contrôle du déclenchement par le serveur :",[505,13836,13837,13840,13843],{},[369,13838,13839],{},"Le serveur sera le propriétaire du pool et des sauvegardes.",[369,13841,13842],{},"Le serveur décide du cycle de vie des sauvegardes (nombre de sauvegarde, durée de vie, ...)",[369,13844,13845],{},"Le serveur peut fusionner les sauvegardes de plusieurs périphériques dans le pool, donc il doit contrôler tout ce qui\ny entre.",[11,13847,13848],{},"Je tiens à souligner que la version finale sera terminée, tous les choix ici présent peuvent avoir été remis en\nquestion. En informatique, faire et défaire, c'est avancer.",[127,13850,13852],{"id":13851},"cinematique-dappel","Cinematique d'appel",[11,13854,13855],{},"Dans cet article, nous allons examiner la cinématique d'appel entre les requêtes et comment elle peut être utilisée\npour améliorer la sauvegarde de contenu en fonction de nos besoins.",[11,13857,13858],{},"Pour sauvegarder le contenu d'un périphérique, celui-ci doit suivre les étapes suivantes :",[505,13860,13861,13875],{},[369,13862,13863,13864],{},"Parcourir l'ensemble de ses fichiers et pour chaque fichier :\n",[505,13865,13866,13869,13872],{},[369,13867,13868],{},"Récupérer les attributs du fichier, tels que le nom, la date de création, de modification, les droits, etc.",[369,13870,13871],{},"Calculer un hash pour déterminer si le fichier est modifié et quelle partie du fichier a été modifiée.",[369,13873,13874],{},"Envoyer cette liste de fichiers (manifest) au serveur de sauvegarde.",[369,13876,13877],{},"Envoyer le contenu nouveau ou modifié des fichiers, chunk par chunk, vers le serveur.",[11,13879,13880],{},"Le processus de sauvegarde peut être optimisé de la manière suivante :",[505,13882,13883,13886],{},[369,13884,13885],{},"Le périphérique peut recevoir la dernière sauvegarde qu'il a effectuée et la comparer avec ce qu'il a de son côté\npour n'envoyer que le différentiel.",[369,13887,13888],{},"Il n'est pas nécessaire de calculer le hash du fichier entier s'il n'a jamais été sauvegardé. Ce hash peut être\ncalculé lors de la récupération des données, ce qui évite de lire deux fois le fichier.",[11,13890,13891],{},"Voici la séquence utilisée pour la sauvegade:",[238,13893,13894],{},[120,13895],{"alt":13896,"src":13897},"Sequence de sauvegarde","https:\u002F\u002Fwww.plantuml.com\u002Fplantuml\u002Fpng\u002FVT5DJiCm40NWlK_nsC6YGVnBNLH5Y1qB12SOsLCo4DkfxScPo3boCTIW_b6HJPJ5wEczpyjSA1NrZJahDNk6fy99o9XtJXqdp1Pu7VeaRRtvhfNdkAhmgANcK6Gbbeh4C75zNU4vT57W53Q6ma7X60t1SGeoV2T69ktuWv9ZdEUIcFp5L86R2Y-I2wFXZ9NOMZXbq6VKClJvqaSdAzdyPMtR97xeio5RfAF2VyACQM9iqKPDi3MjzI3H79zYDblWjzGSjxjLdE4fo8fpoI15tbnesW_Xu8nn_6-1_Svj-5s5f-XRIYGv1b37TkV5HrmdxKyu41LRa0dI-mflGs-r7VeqlkWupDOQllwtc_1vEFGPO_OW4nQZrPA3Kz_y0000",[366,13899,13900,13903,13910,13916],{},[369,13901,13902],{},"Le serveur doit commencer par s'authentifier auprès du client pour garantir que la communication se fait en toute\nsécurité. L'authentification peut se faire via un échange de clés publique\u002Fprivée, avec la connaissance d'un mot de\npasse commun associé au device. L'objectif est que le client puisse s'assurer qu'il communique avec le bon serveur.",[369,13904,13905,13906,13909],{},"Si une sauvegarde a déjà eu lieu par le passé, le serveur peut envoyer au client le contenu de la dernière\nsauvegarde. Cette liste comprend les fichiers avec leurs attributs et leur hash. Cette étape permet au client de\nsavoir quels fichiers ont été ajoutés, supprimés ou modifiés depuis la dernière sauvegarde.",[13907,13908],"br",{},"Par rapport à rsync, le serveur n'a pas besoin de recalculer cette liste.",[369,13911,13912,13913,13915],{},"Le client parcourt les différents dossiers demandés et génère une nouvelle liste. Il peut se contenter de n'envoyer\nque les fichiers qui ont changé depuis la dernière fois.",[13907,13914],{},"Le serveur stocke cela pour préparer la nouvelle sauvegarde. Il est possible d'utiliser un système de journal pour\nécrire les modifications retournées par le client. Ces modifications ne sont persistées dans le fichier final qu'une\nfois la sauvegarde terminée.",[369,13917,13918,13919,13921],{},"Le serveur utilise la liste reçue pour demander au client de lui renvoyer les fichiers modifiés. Si le client a\ncalculé le hash des chunks (morceaux de fichier), le serveur peut ne demander que les morceaux qui ont été modifiés\net pas l'intégralité du fichier.",[13907,13920],{},"Il est important de trouver une taille idéale pour les chunks. Si la taille est trop petite, cela risque de\nmultiplier la quantité de fichiers dans le pool sans aucun bénéfice. Si elle est trop grande, cela peut bloquer le\ntransfert intégral du fichier sur le serveur.",[11,13923,13924],{},"À l'avenir, il pourrait être envisageable d'avoir différents types de chunks pour déterminer ce qui doit être\nrenvoyé dans les transferts réseaux et ceux pour le stockage du pool. Cependant, cela complexifie le stockage.",[127,13926,13928],{"id":13927},"choix-du-protocol","Choix du protocol",[11,13930,13931],{},"Pour envoyer des informations en flux continu du périphérique au serveur et vice versa (un stream pour le manifeste,\nun autre pour les fichiers, un autre pour les logs), le choix de protocole est crucial. Voici quelques options que nous\nallons considérer pour notre shortlist :",[505,13933,13934,13940,13946],{},[369,13935,13936,13939],{},[183,13937,13938],{},"REST",": Protocole simple et facilement utilisable pour transférer des fichiers via upload ou download. Pour le\nstreaming, nous pouvons également envisager l'utilisation de Websocket.",[369,13941,13942,13945],{},[183,13943,13944],{},"gRPC",": Protocole permettant d'utiliser HTTP\u002F2 pour faire des requêtes avec du streaming bidirectionnel.",[369,13947,13948,13951],{},[183,13949,13950],{},"Protocole maison à base de socket",": Maîtrise complète du flux (comme rsync par exemple).",[11,13953,13954],{},"Comme vu précédement, les protocoles basés sur HTTP, comme REST et gRPC, offrent des avantages tels que l'utilisation\nd'un protocole de haut niveau permettant de bénéficier de TLS, de la compression, de la mutualisation de la connexion,\netc.",[11,13956,13957],{},"Le protocole maison, même s'il permettrait de bonnes performances, nécessite une plus grande complexité de\ndéveloppement, mais aussi un plus grand risque de bugs ou de failles de sécurité. Il est donc écarté de notre list\npour le moment, mais nous pourrions l'envisager dans une future refonte si les performances sont vraiment mauvaises.",[11,13959,13960],{},"Pour déterminer le protocole le plus performant pour nos besoins, nous allons comparer les protocoles REST s\u002F HTTP\u002F2 et\ngRPC.",[11,13962,13963],{},"Il est important de noter qu'il est crucial de choisir le langage ou le protocole le plus adapté à notre besoin et ne\npas partir sur des \"a priori\". Souvent, on choisit un langage ou un protocole parce qu'on le connaît bien ou parce\nqu'on l'estime meilleur dans certaines situations. Mais chaque besoin étant différent, l'utilisation dans un contexte\nne signifie pas qu'un langage ou un protocole est le meilleur dans tous les contextes.",[127,13965,13967],{"id":13966},"quel-langage-choisir","Quel langage choisir",[11,13969,13970],{},"Le client de sauvegarde doit être compatible avec Windows, Linux et MacOS.",[11,13972,13973],{},"Ma première approche pour développer un logiciel performant consiste à utiliser le language C++ pour le périphérique\net la partie synchronisation et gestion du pool pour serveur, tout en gardant Node.JS pour l'orchestration.",[11,13975,13976],{},"Comme une partie du serveur est développée en Node.JS, je vais également tester les performances dans ce langage. Si\nj'obtiens des performances similaires, le développement en Node.JS pourrait être plus simple (un seul language pour\ntout et moins de complexité à gérer pour le multithreading).",[11,13978,13979],{},"Si je décide de développer en C++, j'utiliserai Qt pour l'interface graphique, car je le connais bien.",[127,13981,13983],{"id":13982},"lécriture","L'écriture",[11,13985,13986,13988,13989,13992,13993,13996,13997,13999,14000,14002],{},[183,13987,13944],{}," est un framework moderne pour la création de services distants. Il est basé sur ",[183,13990,13991],{},"HTTP\u002F2",", qui offre des\naméliorations de performances significatives par rapport à ",[183,13994,13995],{},"HTTP\u002F1.1",". Pour le protocole ",[183,13998,13938],{},", il existe des\nlibrairies permettant de créer des serveurs et clients ",[183,14001,13991],{}," en C++, mais à mon grand désarroi c'est plus difficile\nque prévu.",[505,14004,14005,14013,14021],{},[369,14006,14007,14012],{},[48,14008,14011],{"href":14009,"rel":14010},"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fcpprestsdk",[346],"cpprestsdk",": Support HTTPS mais pas HTTP\u002F2 (en 2023, il n'évoluera plus),",[369,14014,14015,14020],{},[48,14016,14019],{"href":14017,"rel":14018},"https:\u002F\u002Fnghttp2.org\u002F",[346],"nghttp2",": Librairie très bas niveau (trop bas niveau),",[369,14022,14023,14028],{},[48,14024,14027],{"href":14025,"rel":14026},"https:\u002F\u002Fnghttp2.org\u002Fdocumentation\u002Flibnghttp2_asio.html",[346],"libnghttp2_asio",": Librairie plus haut niveau, possède une\ndépendance sur boost. J'ai également l'impression que cette librairie ne permet pas de faire tout ce qu'on veut en\nHTTP\u002F2 (par exemple le streaming d'un fichier n'est pas quelque chose de facile à écrire, contrairement à l'allocation\ncomplète en mémoire de la réponse à envoyer).",[11,14030,14031,14032,14034],{},"Pour le protocole ",[183,14033,13944],{},", nous avons:",[505,14036,14037],{},[369,14038,14039,2741,14044,14046,14047,178],{},[48,14040,14043],{"href":14041,"rel":14042},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Flanguages\u002Fcpp\u002Fquickstart\u002F",[346],"grpccpp",[183,14045,13944],{}," est de basé basé sur ",[183,14048,13991],{},[11,14050,14051,14052,14055,14056,14059,14060,178],{},"Coté ",[183,14053,14054],{},"Node.JS",", j'utilise ",[183,14057,14058],{},"Nest.JS"," pour la partie serveur, je vais donc m'appuyer sur les librairies suivante pour le\nprotocol ",[183,14061,13944],{},[505,14063,14064,14070],{},[369,14065,14066],{},[48,14067,1775],{"href":14068,"rel":14069},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fgrpc",[346],[369,14071,14072],{},[48,14073,14076],{"href":14074,"rel":14075},"https:\u002F\u002Fdocs.nestjs.com\u002Fmicroservices\u002Fgrpc",[346],"@grpc\u002Fproto-loader",[11,14078,14079,14080,14082,14083,14086],{},"L'utilisation d'",[183,14081,13991],{}," avec Axios (utilisé par Nest.JS) peut se faire en utilisant ",[183,14084,14085],{},"http2-wrapper",":",[2122,14088,14092],{"className":14089,"code":14090,"language":14091,"meta":1645,"style":1645},"language-ts shiki shiki-themes one-dark-pro","import * as http2 from \"http2-wrapper\";\nimport { AxiosRequestConfig } from \"axios\";\n\nconst data = ProtoGetChunkRequest.encode(request).finish();\nreturn this.httpService\n  .request\u003CReadable>({\n    method: \"post\",\n    url: `https:\u002F\u002F${this.hostToBackup}:3000\u002Fget-chunk`,\n    data,\n    transport: http2,\n    responseType: \"stream\",\n  } as AxiosRequestConfig)\n  .pipe(map((response) => response.data));\n","ts",[183,14093,14094,14115,14134,14138,14167,14180,14195,14207,14236,14242,14254,14266,14278],{"__ignoreMap":1645},[2129,14095,14096,14099,14102,14104,14107,14110,14113],{"class":2131,"line":2132},[2129,14097,14098],{"class":2135},"import",[2129,14100,14101],{"class":2422}," *",[2129,14103,9768],{"class":2135},[2129,14105,14106],{"class":2201}," http2",[2129,14108,14109],{"class":2135}," from",[2129,14111,14112],{"class":2220}," \"http2-wrapper\"",[2129,14114,2155],{"class":2143},[2129,14116,14117,14119,14121,14124,14126,14129,14132],{"class":2131,"line":1646},[2129,14118,14098],{"class":2135},[2129,14120,5940],{"class":2143},[2129,14122,14123],{"class":2201},"AxiosRequestConfig",[2129,14125,6169],{"class":2143},[2129,14127,14128],{"class":2135},"from",[2129,14130,14131],{"class":2220}," \"axios\"",[2129,14133,2155],{"class":2143},[2129,14135,14136],{"class":2131,"line":1651},[2129,14137,2179],{"emptyLinePlaceholder":1766},[2129,14139,14140,14142,14145,14147,14150,14152,14155,14157,14160,14162,14165],{"class":2131,"line":2182},[2129,14141,6114],{"class":2135},[2129,14143,14144],{"class":2139}," data",[2129,14146,2206],{"class":2205},[2129,14148,14149],{"class":2139}," ProtoGetChunkRequest",[2129,14151,178],{"class":2143},[2129,14153,14154],{"class":2188},"encode",[2129,14156,2217],{"class":2143},[2129,14158,14159],{"class":2201},"request",[2129,14161,2224],{"class":2143},[2129,14163,14164],{"class":2188},"finish",[2129,14166,2230],{"class":2143},[2129,14168,14169,14172,14175,14177],{"class":2131,"line":2195},[2129,14170,14171],{"class":2135},"return",[2129,14173,14174],{"class":2139}," this",[2129,14176,178],{"class":2143},[2129,14178,14179],{"class":2201},"httpService\n",[2129,14181,14182,14185,14187,14189,14192],{"class":2131,"line":2233},[2129,14183,14184],{"class":2143},"  .",[2129,14186,14159],{"class":2188},[2129,14188,2735],{"class":2143},[2129,14190,14191],{"class":2139},"Readable",[2129,14193,14194],{"class":2143},">({\n",[2129,14196,14197,14200,14202,14205],{"class":2131,"line":2259},[2129,14198,14199],{"class":2201},"    method",[2129,14201,2741],{"class":2143},[2129,14203,14204],{"class":2220},"\"post\"",[2129,14206,3022],{"class":2143},[2129,14208,14209,14212,14214,14217,14220,14223,14225,14228,14231,14234],{"class":2131,"line":2286},[2129,14210,14211],{"class":2201},"    url",[2129,14213,2741],{"class":2143},[2129,14215,14216],{"class":2220},"`https:\u002F\u002F",[2129,14218,14219],{"class":2135},"${",[2129,14221,14222],{"class":2139},"this",[2129,14224,178],{"class":2143},[2129,14226,14227],{"class":2201},"hostToBackup",[2129,14229,14230],{"class":2135},"}",[2129,14232,14233],{"class":2220},":3000\u002Fget-chunk`",[2129,14235,3022],{"class":2143},[2129,14237,14238,14240],{"class":2131,"line":2307},[2129,14239,8161],{"class":2201},[2129,14241,3022],{"class":2143},[2129,14243,14244,14247,14249,14252],{"class":2131,"line":2332},[2129,14245,14246],{"class":2201},"    transport",[2129,14248,2741],{"class":2143},[2129,14250,14251],{"class":2201},"http2",[2129,14253,3022],{"class":2143},[2129,14255,14256,14259,14261,14264],{"class":2131,"line":2350},[2129,14257,14258],{"class":2201},"    responseType",[2129,14260,2741],{"class":2143},[2129,14262,14263],{"class":2220},"\"stream\"",[2129,14265,3022],{"class":2143},[2129,14267,14268,14271,14273,14276],{"class":2131,"line":2564},[2129,14269,14270],{"class":2143},"  } ",[2129,14272,4560],{"class":2135},[2129,14274,14275],{"class":2139}," AxiosRequestConfig",[2129,14277,2975],{"class":2143},[2129,14279,14280,14282,14285,14287,14289,14291,14294,14296,14298,14301,14303,14305],{"class":2131,"line":2570},[2129,14281,14184],{"class":2143},[2129,14283,14284],{"class":2188},"pipe",[2129,14286,2217],{"class":2143},[2129,14288,4965],{"class":2188},[2129,14290,7214],{"class":2143},[2129,14292,14293],{"class":6202},"response",[2129,14295,4026],{"class":2143},[2129,14297,6397],{"class":2135},[2129,14299,14300],{"class":2139}," response",[2129,14302,178],{"class":2143},[2129,14304,8493],{"class":2201},[2129,14306,4135],{"class":2143},[11,14308,14309,14310,14313],{},"Nous allons écrire un fichier de description ",[183,14311,14312],{},"protobuf"," (qui pourra être le même entre la version C++ et Node.JS). Le\nfichier va décrire chaque élément du fichier manifest, ainsi que les appels RPC entre le périphérique et le serveur.",[2122,14315,14318],{"className":14316,"code":14317,"language":14312,"meta":1645,"style":1645},"language-protobuf shiki shiki-themes one-dark-pro","syntax = \"proto3\";\n\npackage woodstock;\n\nenum StatusCode {\n  Ok = 0;\n  Failed = 1;\n}\n\nmessage FileManifest {\n  message FileManifestStat {\n    int32 ownerId = 1;\n    int32 groupId = 2;\n    int64 size = 3;\n    int64 lastRead = 4;\n    int64 lastModified = 5;\n    int64 created = 6;\n    int32 mode = 7;\n  }\n\n  bytes path = 1;\n  FileManifestStat stats = 2;\n  repeated bytes chunks = 3;\n  bytes sha256 = 4;\n}\n\nmessage FileManifestJournalEntry {\n  enum EntryType {\n    ADD = 0;\n    MODIFY = 1;\n    REMOVE = 2;\n  }\n\n  oneof entry {\n    FileManifest manifest = 1;\n    bytes path = 2;\n  }\n  EntryType type = 3;\n}\n\nmessage BackupConfiguration {\n    message Share {\n        string name = 1;\n        repeated string includes = 2;\n        repeated string excludes = 3;\n        string pathPrefix = 4;\n    }\n\n    message Task {\n        string command = 1;\n        repeated Share shares = 2;\n        repeated string includes = 3;\n        repeated string excludes = 4;\n    }\n\n    message Operations {\n        repeated Task tasks = 1;\n        repeated Task finalizedTasks = 2;\n    }\n\n    Operations operations = 1;\n}\n\nmessage FileChunk {\n  bytes data = 1;\n}\n\nmessage PrepareBackupRequest {\n  BackupConfiguration configuration = 1;\n  uint32 lastBackupNumber = 2;\n  uint32 newBackupNumber = 3;\n}\n\nmessage PrepareBackupReply {\n  StatusCode code = 1;\n  bool needRefreshCache = 2;\n}\n\nmessage RefreshCacheReply {\n  StatusCode code = 1;\n}\n\nmessage LaunchBackupRequest {\n  uint32 backupNumber = 1;\n}\n\nmessage GetChunkRequest {\n  bytes filename = 1;\n  uint64 position = 2;\n  uint64 size = 3;\n  bytes sha256 = 4;\n}\n\nservice WoodstockpériphériqueService {\n  rpc PrepareBackup(PrepareBackupRequest) returns (PrepareBackupReply) {}\n\n  rpc RefreshCache(stream FileManifest) returns (RefreshCacheReply) {}\n\n  rpc LaunchBackup(LaunchBackupRequest) returns (stream FileManifestJournalEntry) {}\n\n  rpc GetChunk(GetChunkRequest) returns (stream FileChunk) {}\n}\n",[183,14319,14320,14332,14336,14346,14350,14360,14371,14382,14386,14390,14400,14410,14424,14438,14452,14466,14480,14494,14507,14511,14515,14528,14541,14558,14570,14574,14578,14587,14597,14608,14619,14630,14634,14638,14648,14662,14675,14679,14693,14697,14701,14710,14720,14734,14751,14766,14779,14783,14787,14796,14809,14823,14837,14851,14855,14859,14868,14883,14898,14902,14906,14920,14924,14928,14937,14949,14953,14957,14966,14980,14994,15007,15011,15015,15024,15038,15052,15056,15060,15069,15081,15085,15089,15098,15111,15115,15119,15128,15141,15155,15167,15179,15183,15187,15198,15223,15227,15251,15255,15279,15283,15307],{"__ignoreMap":1645},[2129,14321,14322,14325,14327,14330],{"class":2131,"line":2132},[2129,14323,14324],{"class":2135},"syntax",[2129,14326,2206],{"class":2205},[2129,14328,14329],{"class":2220}," \"proto3\"",[2129,14331,2155],{"class":2143},[2129,14333,14334],{"class":2131,"line":1646},[2129,14335,2179],{"emptyLinePlaceholder":1766},[2129,14337,14338,14341,14344],{"class":2131,"line":1651},[2129,14339,14340],{"class":2135},"package",[2129,14342,14343],{"class":2220}," woodstock",[2129,14345,2155],{"class":2143},[2129,14347,14348],{"class":2131,"line":2182},[2129,14349,2179],{"emptyLinePlaceholder":1766},[2129,14351,14352,14355,14358],{"class":2131,"line":2195},[2129,14353,14354],{"class":2135},"enum",[2129,14356,14357],{"class":2139}," StatusCode",[2129,14359,2905],{"class":2143},[2129,14361,14362,14365,14367,14369],{"class":2131,"line":2233},[2129,14363,14364],{"class":2201},"  Ok",[2129,14366,2206],{"class":2205},[2129,14368,2659],{"class":2422},[2129,14370,2155],{"class":2143},[2129,14372,14373,14376,14378,14380],{"class":2131,"line":2259},[2129,14374,14375],{"class":2201},"  Failed",[2129,14377,2206],{"class":2205},[2129,14379,2599],{"class":2422},[2129,14381,2155],{"class":2143},[2129,14383,14384],{"class":2131,"line":2286},[2129,14385,2353],{"class":2143},[2129,14387,14388],{"class":2131,"line":2307},[2129,14389,2179],{"emptyLinePlaceholder":1766},[2129,14391,14392,14395,14398],{"class":2131,"line":2332},[2129,14393,14394],{"class":2135},"message",[2129,14396,14397],{"class":2139}," FileManifest",[2129,14399,2905],{"class":2143},[2129,14401,14402,14405,14408],{"class":2131,"line":2350},[2129,14403,14404],{"class":2135},"  message",[2129,14406,14407],{"class":2139}," FileManifestStat",[2129,14409,2905],{"class":2143},[2129,14411,14412,14415,14418,14420,14422],{"class":2131,"line":2564},[2129,14413,14414],{"class":2135},"    int32",[2129,14416,14417],{"class":2201}," ownerId",[2129,14419,2206],{"class":2205},[2129,14421,2599],{"class":2422},[2129,14423,2155],{"class":2143},[2129,14425,14426,14428,14431,14433,14436],{"class":2131,"line":2570},[2129,14427,14414],{"class":2135},[2129,14429,14430],{"class":2201}," groupId",[2129,14432,2206],{"class":2205},[2129,14434,14435],{"class":2422}," 2",[2129,14437,2155],{"class":2143},[2129,14439,14440,14443,14445,14447,14450],{"class":2131,"line":2576},[2129,14441,14442],{"class":2135},"    int64",[2129,14444,6255],{"class":2201},[2129,14446,2206],{"class":2205},[2129,14448,14449],{"class":2422}," 3",[2129,14451,2155],{"class":2143},[2129,14453,14454,14456,14459,14461,14464],{"class":2131,"line":2582},[2129,14455,14442],{"class":2135},[2129,14457,14458],{"class":2201}," lastRead",[2129,14460,2206],{"class":2205},[2129,14462,14463],{"class":2422}," 4",[2129,14465,2155],{"class":2143},[2129,14467,14468,14470,14473,14475,14478],{"class":2131,"line":2587},[2129,14469,14442],{"class":2135},[2129,14471,14472],{"class":2201}," lastModified",[2129,14474,2206],{"class":2205},[2129,14476,14477],{"class":2422}," 5",[2129,14479,2155],{"class":2143},[2129,14481,14482,14484,14487,14489,14492],{"class":2131,"line":2604},[2129,14483,14442],{"class":2135},[2129,14485,14486],{"class":2201}," created",[2129,14488,2206],{"class":2205},[2129,14490,14491],{"class":2422}," 6",[2129,14493,2155],{"class":2143},[2129,14495,14496,14498,14501,14503,14505],{"class":2131,"line":2610},[2129,14497,14414],{"class":2135},[2129,14499,14500],{"class":2201}," mode",[2129,14502,2206],{"class":2205},[2129,14504,4728],{"class":2422},[2129,14506,2155],{"class":2143},[2129,14508,14509],{"class":2131,"line":2644},[2129,14510,6310],{"class":2143},[2129,14512,14513],{"class":2131,"line":2664},[2129,14514,2179],{"emptyLinePlaceholder":1766},[2129,14516,14517,14520,14522,14524,14526],{"class":2131,"line":3216},[2129,14518,14519],{"class":2135},"  bytes",[2129,14521,4905],{"class":2201},[2129,14523,2206],{"class":2205},[2129,14525,2599],{"class":2422},[2129,14527,2155],{"class":2143},[2129,14529,14530,14533,14535,14537,14539],{"class":2131,"line":1776},[2129,14531,14532],{"class":2135},"  FileManifestStat",[2129,14534,7422],{"class":2201},[2129,14536,2206],{"class":2205},[2129,14538,14435],{"class":2422},[2129,14540,2155],{"class":2143},[2129,14542,14543,14546,14549,14552,14554,14556],{"class":2131,"line":3248},[2129,14544,14545],{"class":2135},"  repeated",[2129,14547,14548],{"class":2135}," bytes",[2129,14550,14551],{"class":2201}," chunks",[2129,14553,2206],{"class":2205},[2129,14555,14449],{"class":2422},[2129,14557,2155],{"class":2143},[2129,14559,14560,14562,14564,14566,14568],{"class":2131,"line":3278},[2129,14561,14519],{"class":2135},[2129,14563,8484],{"class":2201},[2129,14565,2206],{"class":2205},[2129,14567,14463],{"class":2422},[2129,14569,2155],{"class":2143},[2129,14571,14572],{"class":2131,"line":3293},[2129,14573,2353],{"class":2143},[2129,14575,14576],{"class":2131,"line":3310},[2129,14577,2179],{"emptyLinePlaceholder":1766},[2129,14579,14580,14582,14585],{"class":2131,"line":3330},[2129,14581,14394],{"class":2135},[2129,14583,14584],{"class":2139}," FileManifestJournalEntry",[2129,14586,2905],{"class":2143},[2129,14588,14589,14592,14595],{"class":2131,"line":3335},[2129,14590,14591],{"class":2135},"  enum",[2129,14593,14594],{"class":2139}," EntryType",[2129,14596,2905],{"class":2143},[2129,14598,14599,14602,14604,14606],{"class":2131,"line":3359},[2129,14600,14601],{"class":2201},"    ADD",[2129,14603,2206],{"class":2205},[2129,14605,2659],{"class":2422},[2129,14607,2155],{"class":2143},[2129,14609,14610,14613,14615,14617],{"class":2131,"line":3374},[2129,14611,14612],{"class":2201},"    MODIFY",[2129,14614,2206],{"class":2205},[2129,14616,2599],{"class":2422},[2129,14618,2155],{"class":2143},[2129,14620,14621,14624,14626,14628],{"class":2131,"line":3379},[2129,14622,14623],{"class":2201},"    REMOVE",[2129,14625,2206],{"class":2205},[2129,14627,14435],{"class":2422},[2129,14629,2155],{"class":2143},[2129,14631,14632],{"class":2131,"line":3415},[2129,14633,6310],{"class":2143},[2129,14635,14636],{"class":2131,"line":3434},[2129,14637,2179],{"emptyLinePlaceholder":1766},[2129,14639,14640,14643,14646],{"class":2131,"line":3459},[2129,14641,14642],{"class":2135},"  oneof",[2129,14644,14645],{"class":2201}," entry",[2129,14647,2905],{"class":2143},[2129,14649,14650,14653,14656,14658,14660],{"class":2131,"line":3465},[2129,14651,14652],{"class":2135},"    FileManifest",[2129,14654,14655],{"class":2201}," manifest",[2129,14657,2206],{"class":2205},[2129,14659,2599],{"class":2422},[2129,14661,2155],{"class":2143},[2129,14663,14664,14667,14669,14671,14673],{"class":2131,"line":3480},[2129,14665,14666],{"class":2135},"    bytes",[2129,14668,4905],{"class":2201},[2129,14670,2206],{"class":2205},[2129,14672,14435],{"class":2422},[2129,14674,2155],{"class":2143},[2129,14676,14677],{"class":2131,"line":3486},[2129,14678,6310],{"class":2143},[2129,14680,14681,14684,14687,14689,14691],{"class":2131,"line":3492},[2129,14682,14683],{"class":2135},"  EntryType",[2129,14685,14686],{"class":2201}," type",[2129,14688,2206],{"class":2205},[2129,14690,14449],{"class":2422},[2129,14692,2155],{"class":2143},[2129,14694,14695],{"class":2131,"line":3497},[2129,14696,2353],{"class":2143},[2129,14698,14699],{"class":2131,"line":3516},[2129,14700,2179],{"emptyLinePlaceholder":1766},[2129,14702,14703,14705,14708],{"class":2131,"line":3521},[2129,14704,14394],{"class":2135},[2129,14706,14707],{"class":2139}," BackupConfiguration",[2129,14709,2905],{"class":2143},[2129,14711,14712,14715,14718],{"class":2131,"line":3526},[2129,14713,14714],{"class":2135},"    message",[2129,14716,14717],{"class":2139}," Share",[2129,14719,2905],{"class":2143},[2129,14721,14722,14725,14728,14730,14732],{"class":2131,"line":3549},[2129,14723,14724],{"class":2135},"        string",[2129,14726,14727],{"class":2201}," name",[2129,14729,2206],{"class":2205},[2129,14731,2599],{"class":2422},[2129,14733,2155],{"class":2143},[2129,14735,14736,14739,14742,14745,14747,14749],{"class":2131,"line":3554},[2129,14737,14738],{"class":2135},"        repeated",[2129,14740,14741],{"class":2135}," string",[2129,14743,14744],{"class":2201}," includes",[2129,14746,2206],{"class":2205},[2129,14748,14435],{"class":2422},[2129,14750,2155],{"class":2143},[2129,14752,14753,14755,14757,14760,14762,14764],{"class":2131,"line":3559},[2129,14754,14738],{"class":2135},[2129,14756,14741],{"class":2135},[2129,14758,14759],{"class":2201}," excludes",[2129,14761,2206],{"class":2205},[2129,14763,14449],{"class":2422},[2129,14765,2155],{"class":2143},[2129,14767,14768,14770,14773,14775,14777],{"class":2131,"line":3584},[2129,14769,14724],{"class":2135},[2129,14771,14772],{"class":2201}," pathPrefix",[2129,14774,2206],{"class":2205},[2129,14776,14463],{"class":2422},[2129,14778,2155],{"class":2143},[2129,14780,14781],{"class":2131,"line":3599},[2129,14782,2980],{"class":2143},[2129,14784,14785],{"class":2131,"line":3612},[2129,14786,2179],{"emptyLinePlaceholder":1766},[2129,14788,14789,14791,14794],{"class":2131,"line":3627},[2129,14790,14714],{"class":2135},[2129,14792,14793],{"class":2139}," Task",[2129,14795,2905],{"class":2143},[2129,14797,14798,14800,14803,14805,14807],{"class":2131,"line":3632},[2129,14799,14724],{"class":2135},[2129,14801,14802],{"class":2201}," command",[2129,14804,2206],{"class":2205},[2129,14806,2599],{"class":2422},[2129,14808,2155],{"class":2143},[2129,14810,14811,14813,14815,14817,14819,14821],{"class":2131,"line":3637},[2129,14812,14738],{"class":2135},[2129,14814,14717],{"class":2135},[2129,14816,5398],{"class":2201},[2129,14818,2206],{"class":2205},[2129,14820,14435],{"class":2422},[2129,14822,2155],{"class":2143},[2129,14824,14825,14827,14829,14831,14833,14835],{"class":2131,"line":6959},[2129,14826,14738],{"class":2135},[2129,14828,14741],{"class":2135},[2129,14830,14744],{"class":2201},[2129,14832,2206],{"class":2205},[2129,14834,14449],{"class":2422},[2129,14836,2155],{"class":2143},[2129,14838,14839,14841,14843,14845,14847,14849],{"class":2131,"line":6965},[2129,14840,14738],{"class":2135},[2129,14842,14741],{"class":2135},[2129,14844,14759],{"class":2201},[2129,14846,2206],{"class":2205},[2129,14848,14463],{"class":2422},[2129,14850,2155],{"class":2143},[2129,14852,14853],{"class":2131,"line":6971},[2129,14854,2980],{"class":2143},[2129,14856,14857],{"class":2131,"line":6986},[2129,14858,2179],{"emptyLinePlaceholder":1766},[2129,14860,14861,14863,14866],{"class":2131,"line":7008},[2129,14862,14714],{"class":2135},[2129,14864,14865],{"class":2139}," Operations",[2129,14867,2905],{"class":2143},[2129,14869,14870,14872,14874,14877,14879,14881],{"class":2131,"line":7040},[2129,14871,14738],{"class":2135},[2129,14873,14793],{"class":2135},[2129,14875,14876],{"class":2201}," tasks",[2129,14878,2206],{"class":2205},[2129,14880,2599],{"class":2422},[2129,14882,2155],{"class":2143},[2129,14884,14885,14887,14889,14892,14894,14896],{"class":2131,"line":7058},[2129,14886,14738],{"class":2135},[2129,14888,14793],{"class":2135},[2129,14890,14891],{"class":2201}," finalizedTasks",[2129,14893,2206],{"class":2205},[2129,14895,14435],{"class":2422},[2129,14897,2155],{"class":2143},[2129,14899,14900],{"class":2131,"line":7063},[2129,14901,2980],{"class":2143},[2129,14903,14904],{"class":2131,"line":7068},[2129,14905,2179],{"emptyLinePlaceholder":1766},[2129,14907,14908,14911,14914,14916,14918],{"class":2131,"line":7074},[2129,14909,14910],{"class":2135},"    Operations",[2129,14912,14913],{"class":2201}," operations",[2129,14915,2206],{"class":2205},[2129,14917,2599],{"class":2422},[2129,14919,2155],{"class":2143},[2129,14921,14922],{"class":2131,"line":7106},[2129,14923,2353],{"class":2143},[2129,14925,14926],{"class":2131,"line":7112},[2129,14927,2179],{"emptyLinePlaceholder":1766},[2129,14929,14930,14932,14935],{"class":2131,"line":7123},[2129,14931,14394],{"class":2135},[2129,14933,14934],{"class":2139}," FileChunk",[2129,14936,2905],{"class":2143},[2129,14938,14939,14941,14943,14945,14947],{"class":2131,"line":7129},[2129,14940,14519],{"class":2135},[2129,14942,14144],{"class":2201},[2129,14944,2206],{"class":2205},[2129,14946,2599],{"class":2422},[2129,14948,2155],{"class":2143},[2129,14950,14951],{"class":2131,"line":7151},[2129,14952,2353],{"class":2143},[2129,14954,14955],{"class":2131,"line":7156},[2129,14956,2179],{"emptyLinePlaceholder":1766},[2129,14958,14959,14961,14964],{"class":2131,"line":7192},[2129,14960,14394],{"class":2135},[2129,14962,14963],{"class":2139}," PrepareBackupRequest",[2129,14965,2905],{"class":2143},[2129,14967,14968,14971,14974,14976,14978],{"class":2131,"line":7231},[2129,14969,14970],{"class":2135},"  BackupConfiguration",[2129,14972,14973],{"class":2201}," configuration",[2129,14975,2206],{"class":2205},[2129,14977,2599],{"class":2422},[2129,14979,2155],{"class":2143},[2129,14981,14982,14985,14988,14990,14992],{"class":2131,"line":7236},[2129,14983,14984],{"class":2135},"  uint32",[2129,14986,14987],{"class":2201}," lastBackupNumber",[2129,14989,2206],{"class":2205},[2129,14991,14435],{"class":2422},[2129,14993,2155],{"class":2143},[2129,14995,14996,14998,15001,15003,15005],{"class":2131,"line":7242},[2129,14997,14984],{"class":2135},[2129,14999,15000],{"class":2201}," newBackupNumber",[2129,15002,2206],{"class":2205},[2129,15004,14449],{"class":2422},[2129,15006,2155],{"class":2143},[2129,15008,15009],{"class":2131,"line":7248},[2129,15010,2353],{"class":2143},[2129,15012,15013],{"class":2131,"line":7266},[2129,15014,2179],{"emptyLinePlaceholder":1766},[2129,15016,15017,15019,15022],{"class":2131,"line":7271},[2129,15018,14394],{"class":2135},[2129,15020,15021],{"class":2139}," PrepareBackupReply",[2129,15023,2905],{"class":2143},[2129,15025,15026,15029,15032,15034,15036],{"class":2131,"line":7277},[2129,15027,15028],{"class":2135},"  StatusCode",[2129,15030,15031],{"class":2201}," code",[2129,15033,2206],{"class":2205},[2129,15035,2599],{"class":2422},[2129,15037,2155],{"class":2143},[2129,15039,15040,15043,15046,15048,15050],{"class":2131,"line":7285},[2129,15041,15042],{"class":2135},"  bool",[2129,15044,15045],{"class":2201}," needRefreshCache",[2129,15047,2206],{"class":2205},[2129,15049,14435],{"class":2422},[2129,15051,2155],{"class":2143},[2129,15053,15054],{"class":2131,"line":7303},[2129,15055,2353],{"class":2143},[2129,15057,15058],{"class":2131,"line":7318},[2129,15059,2179],{"emptyLinePlaceholder":1766},[2129,15061,15062,15064,15067],{"class":2131,"line":7323},[2129,15063,14394],{"class":2135},[2129,15065,15066],{"class":2139}," RefreshCacheReply",[2129,15068,2905],{"class":2143},[2129,15070,15071,15073,15075,15077,15079],{"class":2131,"line":7346},[2129,15072,15028],{"class":2135},[2129,15074,15031],{"class":2201},[2129,15076,2206],{"class":2205},[2129,15078,2599],{"class":2422},[2129,15080,2155],{"class":2143},[2129,15082,15083],{"class":2131,"line":7351},[2129,15084,2353],{"class":2143},[2129,15086,15087],{"class":2131,"line":7357},[2129,15088,2179],{"emptyLinePlaceholder":1766},[2129,15090,15091,15093,15096],{"class":2131,"line":7380},[2129,15092,14394],{"class":2135},[2129,15094,15095],{"class":2139}," LaunchBackupRequest",[2129,15097,2905],{"class":2143},[2129,15099,15100,15102,15105,15107,15109],{"class":2131,"line":7411},[2129,15101,14984],{"class":2135},[2129,15103,15104],{"class":2201}," backupNumber",[2129,15106,2206],{"class":2205},[2129,15108,2599],{"class":2422},[2129,15110,2155],{"class":2143},[2129,15112,15113],{"class":2131,"line":7417},[2129,15114,2353],{"class":2143},[2129,15116,15117],{"class":2131,"line":7440},[2129,15118,2179],{"emptyLinePlaceholder":1766},[2129,15120,15121,15123,15126],{"class":2131,"line":7473},[2129,15122,14394],{"class":2135},[2129,15124,15125],{"class":2139}," GetChunkRequest",[2129,15127,2905],{"class":2143},[2129,15129,15130,15132,15135,15137,15139],{"class":2131,"line":7484},[2129,15131,14519],{"class":2135},[2129,15133,15134],{"class":2201}," filename",[2129,15136,2206],{"class":2205},[2129,15138,2599],{"class":2422},[2129,15140,2155],{"class":2143},[2129,15142,15143,15146,15149,15151,15153],{"class":2131,"line":7492},[2129,15144,15145],{"class":2135},"  uint64",[2129,15147,15148],{"class":2201}," position",[2129,15150,2206],{"class":2205},[2129,15152,14435],{"class":2422},[2129,15154,2155],{"class":2143},[2129,15156,15157,15159,15161,15163,15165],{"class":2131,"line":7517},[2129,15158,15145],{"class":2135},[2129,15160,6255],{"class":2201},[2129,15162,2206],{"class":2205},[2129,15164,14449],{"class":2422},[2129,15166,2155],{"class":2143},[2129,15168,15169,15171,15173,15175,15177],{"class":2131,"line":7523},[2129,15170,14519],{"class":2135},[2129,15172,8484],{"class":2201},[2129,15174,2206],{"class":2205},[2129,15176,14463],{"class":2422},[2129,15178,2155],{"class":2143},[2129,15180,15181],{"class":2131,"line":7528},[2129,15182,2353],{"class":2143},[2129,15184,15185],{"class":2131,"line":7533},[2129,15186,2179],{"emptyLinePlaceholder":1766},[2129,15188,15189,15192,15195],{"class":2131,"line":7552},[2129,15190,15191],{"class":2135},"service",[2129,15193,15194],{"class":2139}," Woodstockp",[2129,15196,15197],{"class":2143},"ériphériqueService {\n",[2129,15199,15200,15203,15206,15208,15211,15213,15216,15218,15221],{"class":2131,"line":7574},[2129,15201,15202],{"class":2135},"  rpc",[2129,15204,15205],{"class":2188}," PrepareBackup",[2129,15207,2217],{"class":2143},[2129,15209,15210],{"class":2139},"PrepareBackupRequest",[2129,15212,4026],{"class":2143},[2129,15214,15215],{"class":2135},"returns",[2129,15217,4546],{"class":2143},[2129,15219,15220],{"class":2139},"PrepareBackupReply",[2129,15222,7315],{"class":2143},[2129,15224,15225],{"class":2131,"line":7579},[2129,15226,2179],{"emptyLinePlaceholder":1766},[2129,15228,15229,15231,15234,15236,15238,15240,15242,15244,15246,15249],{"class":2131,"line":9121},[2129,15230,15202],{"class":2135},[2129,15232,15233],{"class":2188}," RefreshCache",[2129,15235,2217],{"class":2143},[2129,15237,7360],{"class":2135},[2129,15239,14397],{"class":2139},[2129,15241,4026],{"class":2143},[2129,15243,15215],{"class":2135},[2129,15245,4546],{"class":2143},[2129,15247,15248],{"class":2139},"RefreshCacheReply",[2129,15250,7315],{"class":2143},[2129,15252,15253],{"class":2131,"line":9126},[2129,15254,2179],{"emptyLinePlaceholder":1766},[2129,15256,15257,15259,15262,15264,15267,15269,15271,15273,15275,15277],{"class":2131,"line":9152},[2129,15258,15202],{"class":2135},[2129,15260,15261],{"class":2188}," LaunchBackup",[2129,15263,2217],{"class":2143},[2129,15265,15266],{"class":2139},"LaunchBackupRequest",[2129,15268,4026],{"class":2143},[2129,15270,15215],{"class":2135},[2129,15272,4546],{"class":2143},[2129,15274,7360],{"class":2135},[2129,15276,14584],{"class":2139},[2129,15278,7315],{"class":2143},[2129,15280,15281],{"class":2131,"line":9173},[2129,15282,2179],{"emptyLinePlaceholder":1766},[2129,15284,15285,15287,15290,15292,15295,15297,15299,15301,15303,15305],{"class":2131,"line":9182},[2129,15286,15202],{"class":2135},[2129,15288,15289],{"class":2188}," GetChunk",[2129,15291,2217],{"class":2143},[2129,15293,15294],{"class":2139},"GetChunkRequest",[2129,15296,4026],{"class":2143},[2129,15298,15215],{"class":2135},[2129,15300,4546],{"class":2143},[2129,15302,7360],{"class":2135},[2129,15304,14934],{"class":2139},[2129,15306,7315],{"class":2143},[2129,15308,15309],{"class":2131,"line":9191},[2129,15310,2353],{"class":2143},[11,15312,15313,15314,15318],{},"Vient ensuite l'écriture de la partie périphérique et de la partie serveur. Je me base sur le\n",[48,15315,15317],{"href":14041,"rel":15316},[346],"Quick Start"," pour la partie C++.",[11,15320,15321,15322,15327],{},"L'exemple se veut simple, et surtout utilise l'API synchrone. gRPC propose également une\n",[48,15323,15326],{"href":15324,"rel":15325},"https:\u002F\u002Fgrpc.io\u002Fdocs\u002Flanguages\u002Fcpp\u002Fasync\u002F",[346],"API asynchrone"," mais qui est plus complexe à mettre en place.",[11,15329,15330],{},"L'API asynchrone devrait permettre d'améliorer les performances dans le cadre d'application multi-threadé. Pour notre\ncas de test nous allons commencer par utiliser l'API synchrone.",[11,15332,15333,15334,15338,15339,178],{},"Dans le cas de l'API synchrone, le serveur reste multi-threadé (et peut donc aussi recevoir plusieurs requêtes en même\ntemps). Vous trouverez les sources de la partie périphérique et de la partie serveur dans le ",[48,15335,15337],{"href":15336},"\u002FWoodstock\u002Fwoodstock-backup-feature_storage-db.zip","ZIP suivant"," dans le dossier ",[183,15340,15341],{},"périphérique-sync",[11,15343,15344,15345,15347],{},"Le développement de la partie ",[183,15346,13991],{}," vient ensuite. La librairie n'est pas des plus faciles d'utilisation, je me\nconcentre sur le développement à des fins de tests de perfs et non de sécurité (ou de non bug).",[127,15349,15351],{"id":15350},"benchmark","Benchmark",[11,15353,15354,15355,15357,15358,15363],{},"Un bench d'envoi de fichier via ",[183,15356,13944],{}," a déjà été fait par d'autres personnes. L'article se trouve ici :\n",[48,15359,15362],{"href":15360,"rel":15361},"https:\u002F\u002Fops.tips\u002Fblog\u002Fsending-files-via-grpc\u002F",[346],"Sending files via gRPC",". Je vous laisse le lire.\nCet article a été fait en Go. Je suppose que le résultat devrait être très proche d'un résultat en C++.",[11,15365,15366,15367,15369,15370,15372],{},"Le résultat de cet article est que du pur ",[183,15368,13991],{}," en Go est tout de même plus performant que ",[183,15371,13944],{}," (probablement du\nà la sérialisation et à la déserialisation des messages qui prend un peu plus de temps).",[11,15374,15375],{},"Pour réaliser les tests, nous avons utilisé le scénario suivant :",[505,15377,15378,15381,15384,15387,15390],{},[369,15379,15380],{},"Sauvegarde d'environ 46 Go de données à partir d'un périphérique équipé d'un SSD ;",[369,15382,15383],{},"Le PC de destination est équipé d'un disque dur connecté en USB 3 ;",[369,15385,15386],{},"Les machines sont reliées par un tuyau de 1 Gb\u002Fs ;",[369,15388,15389],{},"Les chunks sont stockés au format du pool défini dans l'article\n[\u002Fpost\u002Fwoodstock_brtfs](Utilisation de Btrfs et son remplacement) ceci afin de representer au mieux notre cas\nd'utilisation.",[369,15391,15392],{},"Le hash est un SHA3_256, qui est plus performant qu'un SHA2_256.",[11,15394,15395],{},"Les tests ont été réalisés dans des conditions relativement réelles (calcul des hash, copie des fichiers dans un pool\nqui fait de la déduplication), ce qui prend en compte le transfert des fichiers mais aussi les calculs effectués autour.",[11,15397,15398],{},"Une fois le développement effectué, pour compiler la version compatible gRPC, il suffit de lancer la commande suivante :",[2122,15400,15402],{"className":5614,"code":15401,"language":5616,"meta":1645,"style":1645},"cd périphérique-sync\nmkdir build\ncd build\ncmake -DWITH_PROTOCOL_HTTP2=OFF -DWITH_PROTOCOL_GRPC=ON -DCMAKE_BUILD_TYPE=Release ..\u002F\nmake -j16\n\n# Lancement du périphérique\n.\u002Fsrc\u002Fpériphérique_daemon\u002Fpériphérique\n\n# Lancement du serveur\n.\u002Fsrc\u002Fserver\u002Fserver\n",[183,15403,15404,15412,15420,15426,15443,15451,15455,15460,15465,15469,15474],{"__ignoreMap":1645},[2129,15405,15406,15409],{"class":2131,"line":2132},[2129,15407,15408],{"class":2205},"cd",[2129,15410,15411],{"class":2220}," périphérique-sync\n",[2129,15413,15414,15417],{"class":2131,"line":1646},[2129,15415,15416],{"class":2188},"mkdir",[2129,15418,15419],{"class":2220}," build\n",[2129,15421,15422,15424],{"class":2131,"line":1651},[2129,15423,15408],{"class":2205},[2129,15425,15419],{"class":2220},[2129,15427,15428,15431,15434,15437,15440],{"class":2131,"line":2182},[2129,15429,15430],{"class":2188},"cmake",[2129,15432,15433],{"class":2422}," -DWITH_PROTOCOL_HTTP2=OFF",[2129,15435,15436],{"class":2422}," -DWITH_PROTOCOL_GRPC=ON",[2129,15438,15439],{"class":2422}," -DCMAKE_BUILD_TYPE=Release",[2129,15441,15442],{"class":2220}," ..\u002F\n",[2129,15444,15445,15448],{"class":2131,"line":2195},[2129,15446,15447],{"class":2188},"make",[2129,15449,15450],{"class":2422}," -j16\n",[2129,15452,15453],{"class":2131,"line":2233},[2129,15454,2179],{"emptyLinePlaceholder":1766},[2129,15456,15457],{"class":2131,"line":2259},[2129,15458,15459],{"class":2398},"# Lancement du périphérique\n",[2129,15461,15462],{"class":2131,"line":2286},[2129,15463,15464],{"class":2188},".\u002Fsrc\u002Fpériphérique_daemon\u002Fpériphérique\n",[2129,15466,15467],{"class":2131,"line":2307},[2129,15468,2179],{"emptyLinePlaceholder":1766},[2129,15470,15471],{"class":2131,"line":2332},[2129,15472,15473],{"class":2398},"# Lancement du serveur\n",[2129,15475,15476],{"class":2131,"line":2350},[2129,15477,15478],{"class":2188},".\u002Fsrc\u002Fserver\u002Fserver\n",[11,15480,15481],{},"Pour la partie HTTP\u002F2, il faudra inverser les valeurs ON et OFF. Il est important de noter que la configuration du\nserveur est actuellement en dur et liée à la sauvegarde de l'ordinateur. Par conséquent, si vous souhaiter lancer\nle bench chez vous, cette configuration doit être modifiée pour adapter le serveur à votre propre configuration.",[11,15483,15484],{},"Une fois les tests effectués, j'ai réécris uniquement la partie serveur en NodeJS. La réécriture en Node.JS a été plus\nrapide que le développement en C++, car je savais exactement où nous allions et que le Javascript est plus simple à\nécrire (quand on utilise des frameworks comme RxJs, ...).",[11,15486,15487],{},"Voici le résultat du benchmark:",[21,15489,15490,15505],{},[24,15491,15492],{},[27,15493,15494,15497,15501,15503],{},[30,15495],{"align":15496},"left",[30,15498,15500],{"align":15499},"right","rSync",[30,15502,13944],{"align":15499},[30,15504,13991],{"align":15499},[40,15506,15507,15521,15534],{},[27,15508,15509,15512,15515,15518],{},[45,15510,15511],{"align":15496},"Server C++ \u002F Périphérique C++",[45,15513,15514],{"align":15499},"47m",[45,15516,15517],{"align":15499},"3h01",[45,15519,15520],{"align":15499},"2h25",[27,15522,15523,15526,15528,15531],{},[45,15524,15525],{"align":15496},"Server Node.JS \u002F Périphérique C++ (concatMap)",[45,15527],{"align":15499},[45,15529,15530],{"align":15499},"4h57",[45,15532,15533],{"align":15499},"1h50",[27,15535,15536,15539,15541,15544],{},[45,15537,15538],{"align":15496},"Server Node.JS \u002F Périphérique C++ (mergeMap)",[45,15540],{"align":15499},[45,15542,15543],{"align":15499},"60m",[45,15545],{"align":15499},[11,15547,15548,15549,15551,15552,15554,15555,15557],{},"Je n'ai pas testé tous les cas de figure, j'ai principalement fait varier la partie serveur. Et je me suis concentré sur\n",[183,15550,13944],{}," car je voulais comprendre pourquoi ",[183,15553,13991],{}," était beaucoup plus rapide que ",[183,15556,13944],{}," en Node.JS.",[11,15559,15560],{},"Ce que le l'on peut remarquer:",[505,15562,15563,15577,15585,15588],{},[369,15564,15565,15566],{},"En C++ pure:\n",[505,15567,15568,15571,15574],{},[369,15569,15570],{},"rsync est le plus rapide",[369,15572,15573],{},"HTTP\u002F2 est légèrement plus rapide que gRPC",[369,15575,15576],{},"La lenteur est problablement due au fait qu'on soit monothreadé",[369,15578,15579,15580],{},"Sur une écriture monothreadé coté serveur:\n",[505,15581,15582],{},[369,15583,15584],{},"C++ est plus rapide que Node.JS pour gRPC mais pas pour HTTP\u002F2",[369,15586,15587],{},"La parallélisation avec NodeJS est beaucoup plus simple qu'en C++ : il est important de noter que l'amélioration\nvisible en NodeJS vient du fait qu'à partir du moment où l'on ne fait que des I\u002FO en asynchrone, le thread JavaScript\neffectue peu de traitement, ce qui permet d'avoir des traitements asynchrones multi-threadés.",[369,15589,15590],{},"Le transfert est nettement plus lent que rSync (même en HTTP\u002F2)",[11,15592,15593],{},"La grande question est donc : dans quel langage doit-on écrire le périphérique et le serveur au vu de ces résultats ?\nL'écriture en C++ sera plus performante avec l'utilisation de threads. Cependant, le code en C++ sera plus complexe\n(surtout avec le multi-threading) et plus long à écrire, avec des risques de fuites mémoires et de segmentation fault.",[11,15595,15596],{},"L'écriture en JavaScript sera moins performante, mais la parallélisation sera plus facile, et le risque de fuite sera\nmoins élevé. J'ai par contre peur que la consommation mémoire des objets JavaScript soit plus importante que celle des\nobjets en C++.",[1990,15598,15599],{},[11,15600,15601],{},"Note de 2023: Le constat de la consommation mémoire des objets en Node.JS par rapport à C++ est vérifier. Je ferai un\narticle dédié sur le sujet.",[127,15603,15605],{"id":15604},"amélioration-possible-du-bench","Amélioration possible du bench",[505,15607,15608,15618,15626],{},[369,15609,15610,15611,15614,15615,15617],{},"Coté C++: J'ai testé ",[183,15612,15613],{},"RxCpp"," pour ajouter plus d'asynchronisme et traiter les objets en mode flux. Malheureusement,\ncontrairement à Node.JS, la librairie ",[183,15616,15613],{}," est mono-threadé. Du coup cela ne change rien. L'utilisation de RxCpp\nfait aussi des noeuds au cerveau à cause de l'utilisation des templates.",[369,15619,15620,15621,15625],{},"Coté C++: utiliser l'",[48,15622,15624],{"href":15324,"rel":15623},[346],"API Asynchrone"," de gRPC pourrait améliorer les choses.",[369,15627,15628],{},"Coté Node.JS: Il est possible d'améliorer les performances avec des bindings sur des librairies C++.",[127,15630,15632],{"id":15631},"pourquoi-nodejs-pourquoi-pas-nodejs","Pourquoi NodeJS \u002F Pourquoi pas NodeJS",[11,15634,15635],{},"On m'a toujours dit, et je considère que c'est une bonne pratique, d'avancer vite pour avoir une première version et\nensuite d'optimiser ce qui doit l'être (et uniquement ce qui doit l'être).",[11,15637,15638],{},"Clairement écrire en Javascript sera plus rapide que l'écriture en C++. Je pense donc commencer par un MVP en utilisant\nNodeJS. Et ensuite, si le besoin s'en fait sentir, je viendrai réécrire des parties en C++ soit via des bindings, soit\nvia des binaires dédiés.",[11,15640,15641],{},"Je vois déjà les anti NodeJS qui vont me dire que faire un logiciel de sauvegarde avec NodeJS c'est pas terrible. Que\nNodeJS est un language pourris :) ou que c'est un language pour faire des sites Internet, ou que cela utilise un\ninterpreteur.",[11,15643,15644],{},"Mais quand on pense que:",[505,15646,15647,15655],{},[369,15648,15649,15651,15652,15654],{},[183,15650,1012],{}," est fait en ",[183,15653,1062],{}," (avec une partie faite en C depuis la version 4)",[369,15656,15657,15651,15659],{},[183,15658,13789],{},[183,15660,15661],{},"Python",[11,15663,15664],{},"Bref des logiciels de sauvegardes faits avec un language intreprété, il y en a.",[11,15666,15667],{},"Node.JS est basé sur le moteur V8 de Google dont les performances augmentent à chaque version. Donc la réponse pourrait\nsimplement être: pourquoi pas !\nLe plus important c'est de faire un logiciel fiable et qui fonctionne. Donc que l'on utiliser NodeJS, PHP ou autre pour\nfaire le développement, au final : OSEF.",[11,15669,15670],{},"Je terminerai donc sur cette note finale. Je vais finaliser quelques tests et me lancer dans le développement.",[1990,15672,15673,15676,15679],{},[11,15674,15675],{},"Note de 2023 : Finalement, j'ai donc fait le développement en NodeJS. Les performances ont été grandement améliorées\ndepuis le bench. Par contre la consomation en mémoire des objets NodeJS éclate les scores les plus pessimistes\nque j'avais :).",[11,15677,15678],{},"Une fois la première version sortie, je réécrirai certaines parties dans des modules natifs pour améliorer les\nperformances. Voir le client complètement.",[11,15680,15681],{},"Par contre au lieu de choisir C++, je pense partir sur Rust que j'ai commencé à utiliser très récemment. Je ferai un\narticle sur le bench que j'ai pu faire sur la consommation mémoire de nodejs vs rust sur le sujet.",[6033,15683,15684],{},"html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .s_ZVi, html code.shiki .s_ZVi{--shiki-default:#E06C75;--shiki-default-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}",{"title":1645,"searchDepth":1646,"depth":1646,"links":15686},[15687,15692,15693,15694,15695,15696,15697,15698],{"id":13640,"depth":1646,"text":13641,"children":15688},[15689,15690,15691],{"id":13694,"depth":1651,"text":13695},{"id":13770,"depth":1651,"text":13771},{"id":13822,"depth":1651,"text":13823},{"id":13851,"depth":1646,"text":13852},{"id":13927,"depth":1646,"text":13928},{"id":13966,"depth":1646,"text":13967},{"id":13982,"depth":1646,"text":13983},{"id":15350,"depth":1646,"text":15351},{"id":15604,"depth":1646,"text":15605},{"id":15631,"depth":1646,"text":15632},"2023-04-07",{"type":8,"value":15701},[15702,15707,15715],[11,15703,15704],{},[120,15705],{"alt":13592,"src":13599,"className":15706,"width":13601},[125],[1990,15708,15709,15711,15713],{},[11,15710,13606],{},[11,15712,13609],{},[11,15714,13612],{},[11,15716,13615,15717,13619],{},[48,15718,13618],{"href":64},{},{"title":13592,"description":1645},"woodstock_protocol_language_sauvegarde","posts\u002FWoodstock\u002F2021-04-18_woodstock_protocol_language_sauvegarde",[1682,1772,1773,6078,6079],"Ysf2Tbm729RouGvBsMuyaZAuMY3uaQkdpCWyqg6fk7U",{"id":15726,"title":15727,"author":6,"body":15728,"category":1681,"categorySlug":1682,"date":68,"description":13,"excerpt":16886,"extension":1763,"location":1764,"meta":16912,"navigation":1766,"path":64,"published":1766,"seo":16913,"slug":16914,"stem":16915,"tags":16916,"timeToRead":2570,"__hash__":16918},"posts\u002Fposts\u002FWoodstock\u002F2021-01-12_woodstock_brtfs.md","Woodstock Backup - Utilisation de Btrfs et son remplacement",{"type":8,"value":15729,"toc":16872},[15730,15732,15735,15738,15764,15772,15775,15778,15781,15784,15788,15792,15795,15798,15806,15809,15881,15884,16132,16135,16138,16142,16145,16148,16151,16162,16165,16168,16171,16174,16185,16188,16191,16194,16197,16200,16209,16223,16226,16229,16238,16247,16250,16253,16256,16259,16262,16265,16315,16318,16322,16325,16329,16332,16335,16370,16373,16521,16524,16527,16534,16537,16541,16544,16547,16550,16553,16562,16840,16847,16855,16858,16861,16863,16869],[11,15731,13],{},[11,15733,15734],{},"La version 1 de mon programme de sauvegarde Woodstock Backup utlise Btrfs et Rsync pour effectuer une sauvegarde. Je\nl'utilise depuis quelques mois pour sauvegarder mes differentes machines (7 machines).",[11,15736,15737],{},"Voici un premier compte-rendu de l'utilisation de la première version de cet outil dont je suis l'auteur:",[505,15739,15740,15743,15752],{},[369,15741,15742],{},"Lors de mon utilisation la sauvegarde fonctionne très bien, et cela c'est cool :). Je suis aux alentours de 200 snapshots.",[369,15744,15745,15746,15748,15749,15751],{},"J'ai eu un problème d'espace disque. Lors du déplacement de plusieurs énormes fichiers sur un serveur. La taille de\nl'espace de stockage à augmenté énormément.",[13907,15747],{},"En effet rsync ne permet pas de détecter les déplacements de fichiers et btrfs ne permet pas de dédupliquer à la volée\nles données.",[13907,15750],{},"Les fichiers ont donc été considérés comme étant nouveau.",[369,15753,15754,15755,15757,15758,15760,15761,15763],{},"L'espace disque étant tombé à zéro, j'ai voulu supprimer la dernière snapshot pour tester un déplacement de fichiers\ndans btrfs (à la main).",[13907,15756],{},"La suppression de la snapshot a commencé à prendre énormément de temps, puis la machine est devenue inaccessible.",[13907,15759],{},"En me connectant en direct sur la machine (KVM), j'ai découvert que la suppression du dernier volume Btrfs remplissait\nla mémore. Les 8Go octets de mémoire ont été remplis. Et le noyaux linux a utilisé OOM Killer pour détruire tous les\nprocessus.",[13907,15762],{},"Bref la machine n'était plus dans un état lui permettant de faire les sauvegardes.",[11,15765,15766,15767,15771],{},"On parle déjà de problème avec btrfs et la suppression de snapshot (",[48,15768,15769],{"href":15769,"rel":15770},"https:\u002F\u002Fwww.spinics.net\u002Flists\u002Flinux-btrfs\u002Fmsg52088.html",[346],").\nOn trouve aussi pas mal d'article si les problèmes de btrfs quand il n'y a plus d'espace.",[11,15773,15774],{},"Cela me fait penser que btrfs ne me permettrait pas de gérer mes sauvegardes sur le long terme (tant sur le nombre de snapshots\nque cela allait créér que sur la déduplication).",[11,15776,15777],{},"Cela m'amène à la conclusion suivante, il faut que je repense la gestion du pool et de la déduplication. C'est d'ailleurs\nce que font beaucoup de logiciel de sauvegarde (UrBackup proposer Btrfs et une version interne ; BackupPC gère son pool interne,\nBorg gère son pool interne ...)",[11,15779,15780],{},"Je vais donc repenser la manière dont je gère la déduplication dans l'application de sauvegarde. Si je réécris la manière\ndont est géré le stockage, je ne pourrais plus me baser sur rsync pour la sauvegarde. Il faudra donc que je développe mon\npropre outil pour générer la sauvegarde.",[11,15782,15783],{},"Par contre cela à l'avantage de ne plus être dépendant d'un système de fichier.",[127,15785,15787],{"id":15786},"le-pool","Le pool",[132,15789,15791],{"id":15790},"diviser-pour-réigner","Diviser pour réigner",[11,15793,15794],{},"Nous allons donc commencer par construire ce qui doit être le pool des données. Permettant de faire la déduplication.",[11,15796,15797],{},"Afin de faire de la déduplication, nous allons découper les fichiers en plusieurs morceaux. Cela permettra de faire des\nsauvegardes de gros fichiers dont seule une partie change:",[505,15799,15800,15803],{},[369,15801,15802],{},"Comme les images de machine virtuelle par exemple",[369,15804,15805],{},"Comme les fichiers de logs par exemple",[11,15807,15808],{},"Quelle est la bonne taille pour un morceau de fichier. Si on découpe le fichier de taille trop petite alors le découpage ne\nsert à rien. J'ai donc pris un échantillon de tous les fichiers que j'ai sur mes différentes machines pour déterminer la taille\nde cet échantillon.",[2122,15810,15812],{"className":5614,"code":15811,"language":5616,"meta":1645,"style":1645},"find . -type f -print0 | \\\nxargs -0 ls -l | \\\nawk '{ n=int(log($5)\u002Flog(2)); if (n\u003C10) { n=10; } size[n]++ } END { for (i in size) printf(\"%d %d\\n\", 2^i, size[i]) }' | \\\nsort -n | \\\nawk 'function human(x) { x[1]\u002F=1024; if (x[1]>=1024) { x[2]++; human(x) } } { a[1]=$1; a[2]=0; human(a); printf(\"%3d%s: %6d\\n\", a[1],substr(\"kMGTEPYZ\",a[2]+1,1),$2) }'\n",[183,15813,15814,15832,15850,15862,15874],{"__ignoreMap":1645},[2129,15815,15816,15818,15821,15823,15825,15827,15829],{"class":2131,"line":2132},[2129,15817,5666],{"class":2188},[2129,15819,15820],{"class":2220}," .",[2129,15822,5672],{"class":2422},[2129,15824,2202],{"class":2220},[2129,15826,5677],{"class":2422},[2129,15828,5680],{"class":2143},[2129,15830,15831],{"class":2205},"\\\n",[2129,15833,15834,15837,15840,15843,15846,15848],{"class":2131,"line":1646},[2129,15835,15836],{"class":2188},"xargs",[2129,15838,15839],{"class":2422}," -0",[2129,15841,15842],{"class":2220}," ls",[2129,15844,15845],{"class":2422}," -l",[2129,15847,5680],{"class":2143},[2129,15849,15831],{"class":2205},[2129,15851,15852,15855,15858,15860],{"class":2131,"line":1651},[2129,15853,15854],{"class":2188},"awk",[2129,15856,15857],{"class":2220}," '{ n=int(log($5)\u002Flog(2)); if (n\u003C10) { n=10; } size[n]++ } END { for (i in size) printf(\"%d %d\\n\", 2^i, size[i]) }'",[2129,15859,5680],{"class":2143},[2129,15861,15831],{"class":2205},[2129,15863,15864,15867,15870,15872],{"class":2131,"line":2182},[2129,15865,15866],{"class":2188},"sort",[2129,15868,15869],{"class":2422}," -n",[2129,15871,5680],{"class":2143},[2129,15873,15831],{"class":2205},[2129,15875,15876,15878],{"class":2131,"line":2195},[2129,15877,15854],{"class":2188},[2129,15879,15880],{"class":2220}," 'function human(x) { x[1]\u002F=1024; if (x[1]>=1024) { x[2]++; human(x) } } { a[1]=$1; a[2]=0; human(a); printf(\"%3d%s: %6d\\n\", a[1],substr(\"kMGTEPYZ\",a[2]+1,1),$2) }'\n",[11,15882,15883],{},"Voici le resultat de ce test d'échantillon.",[21,15885,15886,15899],{},[24,15887,15888],{},[27,15889,15890,15893,15896],{},[30,15891,15892],{},"File size",[30,15894,15895],{},"Number",[30,15897,15898],{},"Repartition",[40,15900,15901,15912,15923,15934,15945,15956,15967,15978,15989,16000,16011,16022,16033,16044,16055,16066,16077,16088,16099,16110,16121],{},[27,15902,15903,15906,15909],{},[45,15904,15905],{},"1k",[45,15907,15908],{},"29126558",[45,15910,15911],{},"37,36 %",[27,15913,15914,15917,15920],{},[45,15915,15916],{},"2k",[45,15918,15919],{},"8649088",[45,15921,15922],{},"48,45 %",[27,15924,15925,15928,15931],{},[45,15926,15927],{},"4k",[45,15929,15930],{},"7915884",[45,15932,15933],{},"58,60 %",[27,15935,15936,15939,15942],{},[45,15937,15938],{},"8k",[45,15940,15941],{},"6394302",[45,15943,15944],{},"66,81 %",[27,15946,15947,15950,15953],{},[45,15948,15949],{},"16k",[45,15951,15952],{},"4839627",[45,15954,15955],{},"73,01 %",[27,15957,15958,15961,15964],{},[45,15959,15960],{},"32k",[45,15962,15963],{},"3606949",[45,15965,15966],{},"77,64 %",[27,15968,15969,15972,15975],{},[45,15970,15971],{},"64k",[45,15973,15974],{},"3477900",[45,15976,15977],{},"82,10 %",[27,15979,15980,15983,15986],{},[45,15981,15982],{},"128k",[45,15984,15985],{},"5158625",[45,15987,15988],{},"88,72 %",[27,15990,15991,15994,15997],{},[45,15992,15993],{},"256k",[45,15995,15996],{},"3601985",[45,15998,15999],{},"93,34 %",[27,16001,16002,16005,16008],{},[45,16003,16004],{},"512k",[45,16006,16007],{},"971108",[45,16009,16010],{},"94,58 %",[27,16012,16013,16016,16019],{},[45,16014,16015],{},"1M",[45,16017,16018],{},"875574",[45,16020,16021],{},"95,71 %",[27,16023,16024,16027,16030],{},[45,16025,16026],{},"2M",[45,16028,16029],{},"1698194",[45,16031,16032],{},"97,88 %",[27,16034,16035,16038,16041],{},[45,16036,16037],{},"4M",[45,16039,16040],{},"1046430",[45,16042,16043],{},"99,23 %",[27,16045,16046,16049,16052],{},[45,16047,16048],{},"8M",[45,16050,16051],{},"309027",[45,16053,16054],{},"99,62 %",[27,16056,16057,16060,16063],{},[45,16058,16059],{},"16M",[45,16061,16062],{},"105271",[45,16064,16065],{},"99,76 %",[27,16067,16068,16071,16074],{},[45,16069,16070],{},"32M",[45,16072,16073],{},"65211",[45,16075,16076],{},"99,84 %",[27,16078,16079,16082,16085],{},[45,16080,16081],{},"64M",[45,16083,16084],{},"50832",[45,16086,16087],{},"99,91 %",[27,16089,16090,16093,16096],{},[45,16091,16092],{},"128M",[45,16094,16095],{},"33947",[45,16097,16098],{},"99,95 %",[27,16100,16101,16104,16107],{},[45,16102,16103],{},"256M",[45,16105,16106],{},"21338",[45,16108,16109],{},"99,98 %",[27,16111,16112,16115,16118],{},[45,16113,16114],{},"512M",[45,16116,16117],{},"8066",[45,16119,16120],{},"99,99 %",[27,16122,16123,16126,16129],{},[45,16124,16125],{},"> 1G",[45,16127,16128],{},"10068",[45,16130,16131],{},"100,00 %",[11,16133,16134],{},"La conclusion que l'on peut tirer de cela est que si on prend un chunk de 4Mo, alors 99% des fichiers ne seront pas découpés. Dans\nle lot rentrerons la plupart des fichiers textes, des photos, ... . Tous des fichiers qui s'ils ne sont pas des copies, sont normalement\nunique.",[11,16136,16137],{},"Dans une première version, je pense que cette taille ne sera pas paramétrable, mais il faudra la rendre dynamiquement paramétrable dans le\nfutur (si le type des fichiers d'autres personnes a pour conséquence une répartition différente).",[127,16139,16141],{"id":16140},"la-table-de-hashage","La table de hashage",[11,16143,16144],{},"Ces différents morceaux de fichiers (chunk) doivent ensuite pouvoir être identifiés facilement sans pour autant relire le contenu du fichier à\nchaque fois.",[11,16146,16147],{},"L'idée est d'utiliser une table de hashage où avec une clé on peut retrouver le contenu d'un morceau de fichier.",[11,16149,16150],{},"Il faut pouvoir, à partir de la clé:",[505,16152,16153,16156,16159],{},[369,16154,16155],{},"savoir si le morceau de fichier existe,",[369,16157,16158],{},"lire le contenu du morceau de fichier,",[369,16160,16161],{},"écrire le contenu du morceau de fichier.",[11,16163,16164],{},"Quelle clé peut-on utiliser ?",[11,16166,16167],{},"Après quelque recherches je vais partir sur un SHA-256. Quels sont les avantages et les inconvénients d'une telle clé ?",[11,16169,16170],{},"L'avantage est qu'il est possible, quand on connait le hash de faire le lien direct avec le morceau de fichier associé. La clé est\nfacile à générer et le lien entre la clé et le chunk est facile à connaître.",[11,16172,16173],{},"Le plus gros inconvénient est le risque de collision. Le but d'une fonction de hash est de calculer un nombre \"court\" qui permet\nde representer un texte beaucoup plus long. Ce type de fonction de hashage est généralement utilisé pour:",[505,16175,16176,16179,16182],{},[369,16177,16178],{},"quand elle est coupler à une clé public\u002Fprivé, signer un document,",[369,16180,16181],{},"verifier l'intégrité d'un document (en cas d'erreur transfert réseau),",[369,16183,16184],{},"envoyer séparement permet de vérifier que le fichier n'a pas été corrompu.",[11,16186,16187],{},"Ce qui est le propre d'une fonction de hash est sa non réversibilité. En effet s'il était possible de retrouver facilement à partir d'un\nhash le texte contenu associé, les algo de compression n'utiliseraient que ca ;).\nMais la plus grosse conséquence de tout cela est que deux textes (de contenus et de longueurs différentes) peuvent arriver au même hash.",[11,16189,16190],{},"Pour un logiciel de sauvegarde, cela pose alors un gros problème. Si parcequ'il possède le même hash qu'un autre fichier, on ne sauvegarde\npas un document, la restauration ne sera pas faite à l'identique et cela engendrera de la perte de fichier.",[11,16192,16193],{},"Je me suis donc renseigné sur ce qui se faisait ailleurs.",[132,16195,13789],{"id":16196},"borg",[11,16198,16199],{},"Je trouve borg intéressant dans son fonctionnement, ses performances, ainsi que son stockage interne. Ce qui est dommage, c'est que dans\nmon cas, je n'ai pas une vue centralisée des machines dont j'organise la sauvegarde.",[11,16201,16202,16203,16208],{},"Dans la partie ",[48,16204,16207],{"href":16205,"rel":16206},"https:\u002F\u002Fborgbackup.readthedocs.io\u002Fen\u002Fstable\u002Finternals.html",[346],"Internals"," on retrouve la structure interne du pool de Borg.",[11,16210,16211,16212,16217,16218,178],{},"Borg utilise ",[48,16213,16216],{"href":16214,"rel":16215},"https:\u002F\u002Fborgbackup.readthedocs.io\u002Fen\u002Fstable\u002Finternals\u002Fdata-structures.html#hashindex",[346],"HashIndex",", qui si j'ai bien compris\ncalcule un hash sur un contenu dont la taille peut varier. Des utilisateurs se sont déjà posés la question du ",[48,16219,16222],{"href":16220,"rel":16221},"https:\u002F\u002Fgithub.com\u002Fborgbackup\u002Fborg\u002Fissues\u002F170",[346],"risque de collision",[11,16224,16225],{},"La réponse faite par l'équipe de Borg a été de rejeter la demande. L'explication qui est faite est que la probabilité qu'une telle\ncollision arrive est faible sur un hash de 256. Très faible.",[132,16227,13740],{"id":16228},"urbackup",[11,16230,16231,16232,16237],{},"Même chose pour UrBackup. Dans le ",[48,16233,16236],{"href":16234,"rel":16235},"https:\u002F\u002Fwww.urbackup.org\u002Fadministration_manual.html",[346],"manuel d'administration, partie 6.3",",\non retrouve l'information suivante:",[1990,16239,16240],{},[11,16241,16242,16243,16246],{},"UrBackup uses SHA512 to hash the files before file deduplication. In comparison ZFS uses SHA256 for block deduplication.\nThe choice of SHA512 is safer. The Wikipedia page for “Birthday attack” has a probability table for SHA512. According to\nit one needs 1.6 ",[361,16244,16245],{},"1068 different files (of same size) to reach a probability of 10-18 of a collision. It also states\nthat 10-18 is the best case uncorrectable bit error rate of a typical hard disk. To have 1.6"," 1068 different files of\n1KB you need 1.4551915 * 1056 EB of hard disk space. So it is ridiculously more likely that the hard disk returns bad\ndata or the data gets corrupted in RAM, rather than UrBackup linking the wrong files to each other.",[11,16248,16249],{},"Bref, on y explique sur la probabilité de collision pour un SHA512 est plus faible que celle utilisé pour la déduplication\ndans ZFS. On y explique également qu'elle est ridicule par rapport au risque de panne d'un disque dur.",[132,16251,1012],{"id":16252},"backuppc",[11,16254,16255],{},"BackupPC utilise un MD5 pour gérer son les clés de son pool. En cas de collision (ce qui est beaucoup plus probable avec un MD5 qu'avec\nun SHA-256) une extension est ajoutée à la fin du fichier.",[11,16257,16258],{},"Cela necessite alors de ré-envoyer le fichier sur le réseau pour comparer, mais permet de s'assurer qu'en cas de collision on ait une\nsolution de repli.",[11,16260,16261],{},"Un MD5 fait 4 octets contrairement à un SHA-256 qui en fait 32.",[11,16263,16264],{},"Du coup, est-ce qu'avec un md5, j'ai beaucoup de collision sur mon instance BackupPC ? Pour un pool qui contient 3 464 397 fichiers, j'ai\n0 collision.",[2122,16266,16268],{"className":5614,"code":16267,"language":5616,"meta":1645,"style":1645},"> find . -type f | wc -l\n3464397\n> find . -type f | awk 'length($0) > 40' | wc -l\n0\n",[183,16269,16270,16286,16291,16310],{"__ignoreMap":1645},[2129,16271,16272,16275,16277,16280,16283],{"class":2131,"line":2132},[2129,16273,16274],{"class":2143},"> find ",[2129,16276,178],{"class":2205},[2129,16278,16279],{"class":2143}," -type f | ",[2129,16281,16282],{"class":2188},"wc",[2129,16284,16285],{"class":2422}," -l\n",[2129,16287,16288],{"class":2131,"line":1646},[2129,16289,16290],{"class":2188},"3464397\n",[2129,16292,16293,16295,16297,16299,16301,16304,16306,16308],{"class":2131,"line":1651},[2129,16294,16274],{"class":2143},[2129,16296,178],{"class":2205},[2129,16298,16279],{"class":2143},[2129,16300,15854],{"class":2188},[2129,16302,16303],{"class":2220}," 'length($0) > 40'",[2129,16305,5680],{"class":2143},[2129,16307,16282],{"class":2188},[2129,16309,16285],{"class":2422},[2129,16311,16312],{"class":2131,"line":2182},[2129,16313,16314],{"class":2188},"0\n",[11,16316,16317],{},"Ce qui est plutôt rassurant sur le risque de collision sur un SHA-256",[132,16319,16321],{"id":16320},"woodstock-backup","Woodstock Backup",[11,16323,16324],{},"Pour mon propre logiciel, je vais partir sur un SHA-256. Je vais partir du principe qu'il n'y a pas de collision (et je ferais un test\npour vérifier cela).\nDans une version future il pourrait être intéressant d'ajouter une extension au fichier dans le cas où il y a une collision, et laisser\nla possibilité à l'utilisateur de demander à retransférer le fichier pour être sûr qu'il n'y a pas de collision.",[127,16326,16328],{"id":16327},"structure-du-pool","Structure du pool",[11,16330,16331],{},"Maintenant que l'on connaît la taille des différents morceau de fichier ainsi que la clé qui nous servira à les classer. Nous allons\nmaintenant voir comment les organiser dans notre système de fichier.",[11,16333,16334],{},"Derrière le stockage j'ai les idées suivantes:",[505,16336,16337,16344,16347,16364,16367],{},[369,16338,16339,16340,16343],{},"Si on stocke tout les chunks (morceau de fichier) dans un seul dossier, nous pourrions au plus avoir ",[183,16341,16342],{},"1.3 x 10^154"," fichiers. Mais\ncela dépendra plus du nombre de fichier (chunk) que du nombre du nombre de possibilités.",[369,16345,16346],{},"Un système de fichier est en lui-même déjà une table de correspondance où la clé est le nom du fichier.",[369,16348,16349,16350],{},"Il y a une limite sur le nombre de fichiers par dossier:\n",[505,16351,16352,16355,16358,16361],{},[369,16353,16354],{},"FAT32: 65 536 (donc non utilisable)",[369,16356,16357],{},"NTFS: à priori pas de limit.",[369,16359,16360],{},"EXT2: ~1.3 x 10^20 (mais problème de perf au delà de 10 000) - (donc je déconseille son utilisation).",[369,16362,16363],{},"EXT4: pas de limit de nombre de fichier par dossier",[369,16365,16366],{},"J'aimerai à terme (un jour peut-être) pouvoir proposer l'utilisation d'un stockage objet (exemple S3, minio, ...) afin de pouvoir\nutiliser ce stockage depuis plusieurs serveurs. Il n'y a pas de limite du nombre de fichier dans un bucket S3.",[369,16368,16369],{},"Sur une structure à deux niveaux (et 255 possibilité par niveau), on peut espérer que les 3,5 millions de fichiers seront répartis\nce qui ferait un peu plus d'une 50 de fichier par dossier. (le nombre de fichier par dossier dépend du nombre de fichier et de leurs\ntaille).",[11,16371,16372],{},"Voici la structure que j'imagine utiliser pour le stockage des chunks.",[2122,16374,16376],{"className":5614,"code":16375,"language":5616,"meta":1645,"style":1645}," pool\n   ├── aa\n   │    ├── aa\n   │    │    ├── aa\n   │    │    │    ├── REFCNT\n   │    │    │    │     ├── sha256 cnt\n   │    │    │    │     ├── sha256 cnt\n   │    │    │    │     └── sha256 cnt\n   │    │    │    ├── LOCK\n   │    │    │    │     └── host backupNumber\n   │    │    │    └── aaaaaacdefghih-sha256.zlib\n",[183,16377,16378,16383,16391,16401,16412,16425,16443,16459,16476,16489,16507],{"__ignoreMap":1645},[2129,16379,16380],{"class":2131,"line":2132},[2129,16381,16382],{"class":2188}," pool\n",[2129,16384,16385,16388],{"class":2131,"line":1646},[2129,16386,16387],{"class":2188},"   ├──",[2129,16389,16390],{"class":2220}," aa\n",[2129,16392,16393,16396,16399],{"class":2131,"line":1651},[2129,16394,16395],{"class":2188},"   │",[2129,16397,16398],{"class":2220},"    ├──",[2129,16400,16390],{"class":2220},[2129,16402,16403,16405,16408,16410],{"class":2131,"line":2182},[2129,16404,16395],{"class":2188},[2129,16406,16407],{"class":2220},"    │",[2129,16409,16398],{"class":2220},[2129,16411,16390],{"class":2220},[2129,16413,16414,16416,16418,16420,16422],{"class":2131,"line":2195},[2129,16415,16395],{"class":2188},[2129,16417,16407],{"class":2220},[2129,16419,16407],{"class":2220},[2129,16421,16398],{"class":2220},[2129,16423,16424],{"class":2220}," REFCNT\n",[2129,16426,16427,16429,16431,16433,16435,16438,16440],{"class":2131,"line":2233},[2129,16428,16395],{"class":2188},[2129,16430,16407],{"class":2220},[2129,16432,16407],{"class":2220},[2129,16434,16407],{"class":2220},[2129,16436,16437],{"class":2220},"     ├──",[2129,16439,8484],{"class":2220},[2129,16441,16442],{"class":2220}," cnt\n",[2129,16444,16445,16447,16449,16451,16453,16455,16457],{"class":2131,"line":2259},[2129,16446,16395],{"class":2188},[2129,16448,16407],{"class":2220},[2129,16450,16407],{"class":2220},[2129,16452,16407],{"class":2220},[2129,16454,16437],{"class":2220},[2129,16456,8484],{"class":2220},[2129,16458,16442],{"class":2220},[2129,16460,16461,16463,16465,16467,16469,16472,16474],{"class":2131,"line":2286},[2129,16462,16395],{"class":2188},[2129,16464,16407],{"class":2220},[2129,16466,16407],{"class":2220},[2129,16468,16407],{"class":2220},[2129,16470,16471],{"class":2220},"     └──",[2129,16473,8484],{"class":2220},[2129,16475,16442],{"class":2220},[2129,16477,16478,16480,16482,16484,16486],{"class":2131,"line":2307},[2129,16479,16395],{"class":2188},[2129,16481,16407],{"class":2220},[2129,16483,16407],{"class":2220},[2129,16485,16398],{"class":2220},[2129,16487,16488],{"class":2220}," LOCK\n",[2129,16490,16491,16493,16495,16497,16499,16501,16504],{"class":2131,"line":2332},[2129,16492,16395],{"class":2188},[2129,16494,16407],{"class":2220},[2129,16496,16407],{"class":2220},[2129,16498,16407],{"class":2220},[2129,16500,16471],{"class":2220},[2129,16502,16503],{"class":2220}," host",[2129,16505,16506],{"class":2220}," backupNumber\n",[2129,16508,16509,16511,16513,16515,16518],{"class":2131,"line":2350},[2129,16510,16395],{"class":2188},[2129,16512,16407],{"class":2220},[2129,16514,16407],{"class":2220},[2129,16516,16517],{"class":2220},"    └──",[2129,16519,16520],{"class":2220}," aaaaaacdefghih-sha256.zlib\n",[11,16522,16523],{},"Pour les 3 premiers niveaux, nous allons avoir une structure de dossier qui est constituée des 3 premiers octets du SHA-256. Ce qui permet\nde réduire le nombre de fichiers par dossier, mais aussi de limiter le nombre de LOCK lors de la création du pool.",[11,16525,16526],{},"Ensuite dans chaque dossier, nous aurons un fichier de LOCK qui sera créé lors de l'ajout d'un nouveau morceau de fichier. Il sera également\nutilisé lors de la lecture.",[11,16528,16529,16530,16533],{},"Un fichier ",[183,16531,16532],{},"REFCNT"," sera utilisé pour compter le nombre de référence. Cela permettra de supprimer les morceaux quand ces derniers ne seront\nplus utilisés.",[11,16535,16536],{},"Enfin les morceaux de fichiers sont stockés dans des fichiers dont le nom contient le hash. Ces fichiers peuvent être compressés pour réduire\nl'espace utilisé.",[127,16538,16540],{"id":16539},"structure-des-fichiers-de-sauvegardes","Structure des fichiers de sauvegardes",[11,16542,16543],{},"Une fois les morceaux de fichiers déposés dans le pool, il nous faut un fichier permettant de référencer l'ensemble des fichiers qui composent\nla sauvegarde.",[11,16545,16546],{},"Il y aura un fichier par sauvegarde.",[11,16548,16549],{},"Ces fichiers de sauvegarde seront stockés dans les dossiers des différents serveurs sauvegardés. Ce fichier contient l'ensemble des fichiers\nd'une sauvegarde et pour chaque fichier, le hash de l'enssemble des morceaux de fichiers.",[11,16551,16552],{},"Ce fichier doit avoir un format lisible facilement et rapidement par un programme. Ce fichier sera dans un format binaire (pour que le fichier\nsoit lisible rapidement et qu'il ne prenne pas trop de place).",[11,16554,16555,16556,16561],{},"Afin de simplifier la mise en place de ce fichier, nous allons utiliser ",[48,16557,16560],{"href":16558,"rel":16559},"https:\u002F\u002Fdevelopers.google.com\u002Fprotocol-buffers",[346],"protocol-buffers",". Ce\ndernier permet d'avoir un format de fichier compatible quel que soit le language de l'application. Voici un premier jet de format de fichier qui\nsera utilisé pour stocker chaque fichier d'une sauvegarde.",[2122,16563,16565],{"className":14316,"code":16564,"language":14312,"meta":1645,"style":1645},"message FileManifest {\n  message FileManifestStat {\n    int32 ownerId = 1;\n    int32 groupId = 2;\n    int64 size = 3;\n    int64 lastRead = 4;\n    int64 lastModified = 5;\n    int64 created = 6;\n    int32 mode = 7;\n  }\n\n  message FileManifestAcl {\n    string user = 1;\n    string group = 2;\n    int32 mask = 3;\n    int32 other = 4;\n  }\n\n  bytes path = 1;\n  FileManifestStat stats = 2;\n  map\u003Cstring, bytes> xattr = 5;\n  repeated FileManifestAcl acl = 6;\n  repeated bytes chunks = 3;\n  bytes sha256 = 4;\n}\n",[183,16566,16567,16575,16583,16595,16607,16619,16631,16643,16655,16667,16671,16675,16684,16698,16711,16724,16737,16741,16745,16757,16769,16795,16810,16824,16836],{"__ignoreMap":1645},[2129,16568,16569,16571,16573],{"class":2131,"line":2132},[2129,16570,14394],{"class":2135},[2129,16572,14397],{"class":2139},[2129,16574,2905],{"class":2143},[2129,16576,16577,16579,16581],{"class":2131,"line":1646},[2129,16578,14404],{"class":2135},[2129,16580,14407],{"class":2139},[2129,16582,2905],{"class":2143},[2129,16584,16585,16587,16589,16591,16593],{"class":2131,"line":1651},[2129,16586,14414],{"class":2135},[2129,16588,14417],{"class":2201},[2129,16590,2206],{"class":2205},[2129,16592,2599],{"class":2422},[2129,16594,2155],{"class":2143},[2129,16596,16597,16599,16601,16603,16605],{"class":2131,"line":2182},[2129,16598,14414],{"class":2135},[2129,16600,14430],{"class":2201},[2129,16602,2206],{"class":2205},[2129,16604,14435],{"class":2422},[2129,16606,2155],{"class":2143},[2129,16608,16609,16611,16613,16615,16617],{"class":2131,"line":2195},[2129,16610,14442],{"class":2135},[2129,16612,6255],{"class":2201},[2129,16614,2206],{"class":2205},[2129,16616,14449],{"class":2422},[2129,16618,2155],{"class":2143},[2129,16620,16621,16623,16625,16627,16629],{"class":2131,"line":2233},[2129,16622,14442],{"class":2135},[2129,16624,14458],{"class":2201},[2129,16626,2206],{"class":2205},[2129,16628,14463],{"class":2422},[2129,16630,2155],{"class":2143},[2129,16632,16633,16635,16637,16639,16641],{"class":2131,"line":2259},[2129,16634,14442],{"class":2135},[2129,16636,14472],{"class":2201},[2129,16638,2206],{"class":2205},[2129,16640,14477],{"class":2422},[2129,16642,2155],{"class":2143},[2129,16644,16645,16647,16649,16651,16653],{"class":2131,"line":2286},[2129,16646,14442],{"class":2135},[2129,16648,14486],{"class":2201},[2129,16650,2206],{"class":2205},[2129,16652,14491],{"class":2422},[2129,16654,2155],{"class":2143},[2129,16656,16657,16659,16661,16663,16665],{"class":2131,"line":2307},[2129,16658,14414],{"class":2135},[2129,16660,14500],{"class":2201},[2129,16662,2206],{"class":2205},[2129,16664,4728],{"class":2422},[2129,16666,2155],{"class":2143},[2129,16668,16669],{"class":2131,"line":2332},[2129,16670,6310],{"class":2143},[2129,16672,16673],{"class":2131,"line":2350},[2129,16674,2179],{"emptyLinePlaceholder":1766},[2129,16676,16677,16679,16682],{"class":2131,"line":2564},[2129,16678,14404],{"class":2135},[2129,16680,16681],{"class":2139}," FileManifestAcl",[2129,16683,2905],{"class":2143},[2129,16685,16686,16689,16692,16694,16696],{"class":2131,"line":2570},[2129,16687,16688],{"class":2135},"    string",[2129,16690,16691],{"class":2201}," user",[2129,16693,2206],{"class":2205},[2129,16695,2599],{"class":2422},[2129,16697,2155],{"class":2143},[2129,16699,16700,16702,16705,16707,16709],{"class":2131,"line":2576},[2129,16701,16688],{"class":2135},[2129,16703,16704],{"class":2201}," group",[2129,16706,2206],{"class":2205},[2129,16708,14435],{"class":2422},[2129,16710,2155],{"class":2143},[2129,16712,16713,16715,16718,16720,16722],{"class":2131,"line":2582},[2129,16714,14414],{"class":2135},[2129,16716,16717],{"class":2201}," mask",[2129,16719,2206],{"class":2205},[2129,16721,14449],{"class":2422},[2129,16723,2155],{"class":2143},[2129,16725,16726,16728,16731,16733,16735],{"class":2131,"line":2587},[2129,16727,14414],{"class":2135},[2129,16729,16730],{"class":2201}," other",[2129,16732,2206],{"class":2205},[2129,16734,14463],{"class":2422},[2129,16736,2155],{"class":2143},[2129,16738,16739],{"class":2131,"line":2604},[2129,16740,6310],{"class":2143},[2129,16742,16743],{"class":2131,"line":2610},[2129,16744,2179],{"emptyLinePlaceholder":1766},[2129,16746,16747,16749,16751,16753,16755],{"class":2131,"line":2644},[2129,16748,14519],{"class":2135},[2129,16750,4905],{"class":2201},[2129,16752,2206],{"class":2205},[2129,16754,2599],{"class":2422},[2129,16756,2155],{"class":2143},[2129,16758,16759,16761,16763,16765,16767],{"class":2131,"line":2664},[2129,16760,14532],{"class":2135},[2129,16762,7422],{"class":2201},[2129,16764,2206],{"class":2205},[2129,16766,14435],{"class":2422},[2129,16768,2155],{"class":2143},[2129,16770,16771,16774,16776,16779,16781,16784,16786,16789,16791,16793],{"class":2131,"line":3216},[2129,16772,16773],{"class":2135},"  map",[2129,16775,2735],{"class":2143},[2129,16777,16778],{"class":2135},"string",[2129,16780,931],{"class":2143},[2129,16782,16783],{"class":2135},"bytes",[2129,16785,2747],{"class":2143},[2129,16787,16788],{"class":2201},"xattr",[2129,16790,2206],{"class":2205},[2129,16792,14477],{"class":2422},[2129,16794,2155],{"class":2143},[2129,16796,16797,16799,16801,16804,16806,16808],{"class":2131,"line":1776},[2129,16798,14545],{"class":2135},[2129,16800,16681],{"class":2135},[2129,16802,16803],{"class":2201}," acl",[2129,16805,2206],{"class":2205},[2129,16807,14491],{"class":2422},[2129,16809,2155],{"class":2143},[2129,16811,16812,16814,16816,16818,16820,16822],{"class":2131,"line":3248},[2129,16813,14545],{"class":2135},[2129,16815,14548],{"class":2135},[2129,16817,14551],{"class":2201},[2129,16819,2206],{"class":2205},[2129,16821,14449],{"class":2422},[2129,16823,2155],{"class":2143},[2129,16825,16826,16828,16830,16832,16834],{"class":2131,"line":3278},[2129,16827,14519],{"class":2135},[2129,16829,8484],{"class":2201},[2129,16831,2206],{"class":2205},[2129,16833,14463],{"class":2422},[2129,16835,2155],{"class":2143},[2129,16837,16838],{"class":2131,"line":3293},[2129,16839,2353],{"class":2143},[11,16841,16842,16843,16846],{},"Le fichier sera constitué d'une liste de ",[183,16844,16845],{},"FileManifest",". Ce fichier est de la forme :",[2122,16848,16853],{"className":16849,"code":16851,"language":16852},[16850],"language-text","int32 FileManifest int32 FileManifest int32 FileManifest int32 FileManifest int32 FileManifest int32 FileManifest int32 FileManifest\nint32 FileManifest int32 FileManifest int32 FileManifest int32 FileManifest int32 FileManifest int32 FileManifest int32 FileManifest\n(vous avez compris le principe)\n","text",[183,16854,16851],{"__ignoreMap":1645},[11,16856,16857],{},"où chaque int32 est la taille de chaque FileManifest.",[11,16859,16860],{},"Lors de la sauvegarde, le serveur repartira de la sauvegarde précédente et écrira toute modification dans un journal. Le journal\nindiquera, les fichiers ajoutés, supprimés, et modifiés. Une fois la sauvegarde terminée, le journal sera fusionné dans le fichier de\nsauvegarde (et le comptage de référence sera mis à jour).",[127,16862,1620],{"id":1619},[11,16864,16865,16866,178],{},"Maintenant que j'ai une idée de la forme du nouveau de format de stockage, il ne me reste plus qu'à développer le pool de stockage.\nSi vous avez des retours à me faire sur le format de stockage alors n'hésitez pas à me contacter. Vous pouvez le faire par exemple sur\n",[48,16867,16868],{"href":16320},"https:\u002F\u002Fgithub.com\u002Fphoenix741\u002Fwoodstock-backup",[6033,16870,16871],{},"html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}",{"title":1645,"searchDepth":1646,"depth":1646,"links":16873},[16874,16877,16883,16884,16885],{"id":15786,"depth":1646,"text":15787,"children":16875},[16876],{"id":15790,"depth":1651,"text":15791},{"id":16140,"depth":1646,"text":16141,"children":16878},[16879,16880,16881,16882],{"id":16196,"depth":1651,"text":13789},{"id":16228,"depth":1651,"text":13740},{"id":16252,"depth":1651,"text":1012},{"id":16320,"depth":1651,"text":16321},{"id":16327,"depth":1646,"text":16328},{"id":16539,"depth":1646,"text":16540},{"id":1619,"depth":1646,"text":1620},{"type":8,"value":16887},[16888,16890,16892,16894],[11,16889,13],{},[11,16891,15734],{},[11,16893,15737],{},[505,16895,16896,16898,16904],{},[369,16897,15742],{},[369,16899,15745,16900,15748,16902,15751],{},[13907,16901],{},[13907,16903],{},[369,16905,15754,16906,15757,16908,15760,16910,15763],{},[13907,16907],{},[13907,16909],{},[13907,16911],{},{"planet":1766},{"title":15727,"description":13},"woodstock_brtfs","posts\u002FWoodstock\u002F2021-01-12_woodstock_brtfs",[1772,1773,16917,982,6078,6079,1682],"btrfs","f12bC0YlcreB0kEuvkJiJsi0nYRb0O3yavG9pPjrXUM",{"id":16920,"title":51,"author":6,"body":16921,"category":1681,"categorySlug":1682,"date":54,"description":13,"excerpt":17659,"extension":1763,"location":1764,"meta":17688,"navigation":1766,"path":50,"published":1766,"seo":17689,"slug":1682,"stem":17690,"tags":17691,"timeToRead":2564,"__hash__":17692},"posts\u002Fposts\u002FWoodstock\u002F2020-09-20_woodstock.md",{"type":8,"value":16922,"toc":17644},[16923,16925,16935,16940,16944,16953,16961,16964,16967,16972,16980,16985,16990,17028,17031,17035,17038,17064,17067,17073,17075,17081,17087,17090,17093,17096,17099,17105,17108,17119,17122,17125,17127,17134,17140,17143,17146,17151,17159,17162,17168,17172,17178,17183,17186,17195,17197,17200,17203,17206,17210,17213,17271,17275,17289,17293,17300,17303,17315,17324,17327,17338,17342,17345,17367,17370,17374,17419,17422,17426,17434,17443,17446,17507,17510,17603,17606,17609,17612],[11,16924,13],{},[11,16926,16927,16928,16932,16933,178],{},"Un projet ",[48,16929,16931],{"href":16930},"\u002Fpost\u002F2020-07-27-fin-passprotect\u002F","s'en va"," et un autre commence.\nJe suis heureux de vous présenter ce nouveau projet: ",[154,16934,16321],{},[16936,16937,16939],"h1",{"id":16938},"genèse-du-projet","Genèse du projet",[127,16941,16943],{"id":16942},"mes-problèmes","Mes problèmes",[11,16945,16946,16947,16952],{},"Pour faire des sauvegardes, j'utilisais jusqu'ici ",[154,16948,16949],{},[48,16950,1012],{"href":13731,"rel":16951},[346],". C'est un très bon logiciel pour effectuer des sauvegardes de plusieurs machines sur une instance centralisée.",[11,16954,16955,16957,16958,16960],{},[154,16956,1012],{}," est écrit en ",[361,16959,1062],{}," avec des partie en C. En effet il se base sur un fork de rsync qui permet d'enregistrer le résultat des sauvegardes dans un format qui lui est propre. Malgré qu'il fonctionne très bien j'ai eu plusieurs problèmes avec récemment.",[11,16962,16963],{},"Il permet de faire des sauvegardes lancées depuis un serveur centralisé. Ce dernier vérifie régulièrement si les différents PC à sauvegarder sont présents sur le réseau et s'y connecte pour faire les sauvegardes sur la base d'un calendrier.",[11,16965,16966],{},"Le serveur a besoin de pouvoir se connecter sur tous les clients (incovénient que je suis prêt à accepter) mais les clients n'ont du coup pas besoin de devoir se connecter au serveur (avec le risque qu'un client compromis, compromette le serveur).",[11,16968,16969,16971],{},[154,16970,1012],{}," est également capable de décider de l'heure de la sauvegarde en fonction de la date de la dernière sauvegarde ainsi que de la présence du PC sur le réseau. Ainsi si un PC est toujours présent, il peut être sauvegardé tout en préservant les heures où la personne a besoin de toute la puissance de son PC, sauf si la sauvegarde date de trop.",[11,16973,16974,16975,16979],{},"Je vous invite fortement à aller à l'adresse ",[48,16976,13731],{"href":16977,"rel":16978},"https:\u002F\u002Fbackuppc.github.io\u002Fbackuppc\u002Finfo.html",[346]," pour en savoir plus sur BackupPC.",[11,16981,16982],{},[120,16983],{"alt":1012,"src":16984},"\u002FWoodstock\u002Fbackuppc.png",[11,16986,16987,16988,5840],{},"Voici les quelques problèmes que j'ai eu récemment avec les dernières versions de ",[154,16989,1012],{},[505,16991,16992,17001],{},[369,16993,16994,16995,16997,16998,17000],{},"Lors du passage de la version v3 à la version v4, et alors que le pool de sauvegarde était stocké sur un partage NFS, je me suis mis à perdre des fichiers: Impossible de restaurer des fichiers.",[13907,16996],{},"Après quelques recherches j'ai découvert que BackupPC v4 n'était pas compatible avec NFS, et j'ai dû basculer toute mon installation sur un disque iSCSI sur mon NAS.",[13907,16999],{},"J'ai alors perdu mon historique de sauvegarde (je n'avais alors pas assez de place pour faire faire mes sauvegardes dans un coin et des expérimentations\u002Fmigrations de l'autre).",[369,17002,17003,17004,17006,17008,17009,17011,17012,17014,17015,17017,17018,17020,17021,17023,178],{},"Une fois les sauvegardes effectuées je souhaite pouvoir copier le résultat sur un disque dur USB (qui contient donc alors la dernière version). Cette archive est alors déposée régulièrement hors site afin de minimiser le risque de perte de données.",[13907,17005],{},[154,17007,1012],{}," permet de le faire nativement en exportant des archives au format TAR sur ce disque dur USB (moyennant une pré-étape pour monter le disque, et une post-étape pour le démonter).",[13907,17010],{},"Cela me pose problème, en effet je veux pouvoir facilement accéder au contenu de l'archive (donc sans ouvrir le tar), et cette dernière est également très longue à créer.\nDe plus il m'est déjà arrivé d'ouvrir un tar et de me rendre compte que l'archive était corrompue (tronquée) soit par manque de place soit car l'archivage n'avait pas fonctionné correctement mais sans avertissement.",[13907,17013],{},"Je m'emploie donc à créer un script qui à l'aide d'un connecteur Fuse BackupPC me permet de synchroniser les dernières versions des sauvegardes avec rsync vers le disque dur USB.",[13907,17016],{},"Malheureusement le script FUSE n'est pas très maintenu, et pose problème avec des fichiers de plusieurs centaines de gigaoctets, ou avec les permissions de fichiers sauvegardés avec des machines Windows. J'ai donc adapté le script Fuse à mes problèmes pour régler uniquement le problème de droits.",[13907,17019],{},"La modification n'est pas super propre: je squeeze les droits si l'utilisateur à un uid particulier.",[13907,17022],{},[48,17024,17027],{"href":17025,"rel":17026},"https:\u002F\u002Fgist.github.com\u002Fphoenix741\u002F99a5076569b01ba5a116cec24a798d5f",[346],"Vous pouvez retrouver ma modification ici",[11,17029,17030],{},"Le fait est que même si la solution continue de tourner actuellement et fonctionne bien, j'ai perdu une partie de la confiance que j'ai en ce produit.",[127,17032,17034],{"id":17033},"etudes-des-autres-solutions","Etudes des autres solutions",[11,17036,17037],{},"Quelques critères:",[505,17039,17040,17046,17049,17052,17055,17058,17061],{},[369,17041,17042,17043,178],{},"Déjà un premier critère est que la solution doit être ",[361,17044,17045],{},"Open Source",[369,17047,17048],{},"Je ne veux pas de client lourd pour visualiser mes sauvegardes (seul un client léger pour y accéder de n'importe où).",[369,17050,17051],{},"Je veux pouvoir facilement créer des archives sur des disques durs USB à plat (sans format spécial comme zip, tar, ...)",[369,17053,17054],{},"Je dois pouvoir sauvegarder des machines sous Windows ou sous Linux.",[369,17056,17057],{},"Je suis prêt à installer un client lourd sur le client qui est sauvegardé.",[369,17059,17060],{},"Je dois sauvegarder 6 machines (1 serveur dédié, 2 Vieux PC portable, 2 Vieux PC Fixe, 1 Vieux NAS) sans que les utilisateurs (ma femme, moi) n'aient à y penser.",[369,17062,17063],{},"Si le logiciel est facilement contrôlable par API et via une IHM simple d'utilisation, c'est un grand plus.",[11,17065,17066],{},"J'ai regardé plusieurs solutions et voici les deux solutions qui m'ont tapé dans l'oeil (et ça fait mal) en plus de BackupPC que j'utilse déjà.",[11,17068,17069,17070,17072],{},"Je précise que mes tests sont alors limités car je n'ai pas assez d'espace de stockage pour avoir à la fois le pool de ",[154,17071,1012],{}," de environ 2To et le pool d'un autre gestionnaire de sauvegarde en même temps.",[132,17074,13740],{"id":16228},[11,17076,17077,17078,178],{},"J'ai alors installé ",[48,17079,13740],{"href":13738,"rel":17080},[346],[11,17082,17083,17084,17086],{},"J'ai apprécié l'utiliser en utilisant le stockage ",[361,17085,16917],{},". Il permet aussi d'utiliser un système de stockage qui lui est propre.",[11,17088,17089],{},"Je n'ai pas trouvé de méthode pour effectuer de l'archivage sur disque dur USB des dernières sauvegardes uniquement (et pas l'ensemble du pool) pour stockage off-site. Ce point pourrait être resolvable en utilisant justement le stockage Btrfs et en écrivant mes propres scripts.",[11,17091,17092],{},"Sous Windows, il permet aussi de créer des Snapshots (équivalent LVM) pour permettre de faire des sauvegardes de fichier en lecture.",[11,17094,17095],{},"Il aussi capable de faire des images disque des machines Windows.",[11,17097,17098],{},"UrBackup utilise un client à installer sur chaque ordinateur à sauvegarder. Cela ne pose pas de problème et peut permettre d'optimiser la vitesse de sauvegarde par rapport à un simple rsync, et d'avoir un format de stockage propre pour optimiser la taille occupé.",[11,17100,17101,17102,17104],{},"Bref, sur le papier ",[154,17103,13740],{}," a tout pour plaire.",[11,17106,17107],{},"Pourquoi ne pas l'utiliser ? Bonne question :).",[11,17109,17110,17111,17114,17115,17118],{},"La migration de mon historique de sauvegarde de BackupPC vers UrBackup n'était pas possible facilement. Il aurait fallu pour cela qu'en plus de générer la structure ",[361,17112,17113],{},"Btrfs"," qui va bien créer la structure dans la base ",[361,17116,17117],{},"Sqlite",", ce que je n'ai pas forcément trouvé pratique.",[11,17120,17121],{},"Il me semble aussi que je ne trouvais pas l'interface très pratique (entre autres pour la restauration de fichiers).",[11,17123,17124],{},"Je n'ai pas plus de raisons que cela de ne pas l'utiliser, donc si vous cherchez un logiciel pour faire des sauvegardes n'hésitez pas à le tester.",[132,17126,13789],{"id":16196},[11,17128,17129,17130,178],{},"J'ai aimé utiliser également ",[48,17131,13789],{"href":17132,"rel":17133},"https:\u002F\u002Fborgbackup.readthedocs.io\u002F",[346],[11,17135,17136,17137,17139],{},"Surtout la possibilité de pouvoir chiffrer les sauvegardes, ainsi que les performances de ",[154,17138,13789],{}," mêmes.",[11,17141,17142],{},"Malheureusement, je souhaite pouvoir facilement déchiffrer le contenu depuis le serveur principal pour archiver plusieurs sauvegardes de plusieurs machines en même temps.",[11,17144,17145],{},"Je souhaite également pouvoir sauvegarder toute sortes d'ordinateurs. Certains appartenant au réseau local, d'autres se trouvant être des dédiés sur Internet. Des PC sous Linux mais aussi un PC sous Windows.",[11,17147,17148,17150],{},[154,17149,13789],{}," m'aurait alors posé quelques soucis sous Windows.",[11,17152,17153,17154],{},"Je ne souhaite pas que mes serveurs dédiés aient accès à mon réseau local pour faire les sauvegardes, ni aient accès aux serveurs de backup directement au risque de compromettre la sécurité. ",[48,17155,17158],{"href":17156,"rel":17157},"https:\u002F\u002Fborgbackup.readthedocs.io\u002Fen\u002Fstable\u002Ffaq.html#how-can-i-protect-against-a-hacked-backup-client",[346],"Il faut alors paramétrer le serveur de backup pour gérer cela au niveau de ssh",[11,17160,17161],{},"Il n'est pas possible non plus de mutualiser les sauvegardes dans un seul repo, ce qui au delà des problèmes de sécurité, pose également des problèmes de lock.",[11,17163,17164,17165,17167],{},"Je pense que ",[154,17166,13789],{}," est par contre un très bon logiciel pour faire une sauvegarde de son PC perso quand on ne s'occupe que de soi et qu'on a qu'un seul PC.",[132,17169,17171],{"id":17170},"autres-tests","Autres tests",[11,17173,17174,17175,17177],{},"J'ai également testé rapidement ",[154,17176,13803],{}," pour lequel je jette un oeil également régulièrement, mais l'IHM non intégré était alors très lente lors de mes tests, et la ligne de commande me rendait la restauration de fichiers complexe lors du peu de fois que je l'ai utilisé.",[11,17179,17180,17181,178],{},"Il est alors difficile de trouver un concurrent qui me convienne pour remplacer ",[154,17182,1012],{},[11,17184,17185],{},"Si vous pensez que je me suis trompé sur les tests que j'ai fait ci-dessus, ou que vous voyez un autre logiciel de sauvegardes qui pourrait me convenir, n'hésitez pas à m'envoyer un mail.",[11,17187,17188,17189,178],{},"A l'heure actuelle j'ai décidé d'écrire mon propre système de sauvegarde que j'ai nommé ",[154,17190,17191],{},[48,17192,16321],{"href":17193,"rel":17194},"https:\u002F\u002Fwoodstock.shadoware.org\u002F",[346],[16936,17196,16321],{"id":16320},[11,17198,17199],{},"A défaut de tourner en rond et de ne pas être complètement satisfait, j'ai décidé de développer ma propre solution. Ainsi si je n'ai pas toutes les fonctionnalités que je désire, je n'ai qu'à m'en prendre à moi-même et les développer.",[11,17201,17202],{},"Il n'est pas facile de choisir un nom pour un logiciel.",[11,17204,17205],{},"Lors du développement de cette application de sauvegarde, je regardais un épisode d'une série dont l'histoire se passait à Woodstock, d'où le nom :).",[127,17207,17209],{"id":17208},"les-fonctionnalités","Les fonctionnalités",[11,17211,17212],{},"Quels sont les fonctionnalités à l'heure actuelle :",[505,17214,17215,17218,17221,17224,17227,17237,17242,17258,17268],{},[369,17216,17217],{},"Faire des sauvegardes de façon régulière (par exemple une fois par jour) et lors de la dispo de l'ordinateur.",[369,17219,17220],{},"Pour chaque sauvegarde il est possible d'éxecuter des étapes en amont et en aval, voir de faire plusieurs sauvegardes de plusieurs dossiers.",[369,17222,17223],{},"On peut lister les sauvegardes pour chaque hôte.",[369,17225,17226],{},"On peut télécharger les fichiers un par un ou en téléchargeant un fichier Zip.",[369,17228,17229,17230,17232,17233,17236],{},"Les sauvegardes sont stockées sur un système de fichier ",[361,17231,17113],{}," ce qui permet de bénéficier du système de ",[361,17234,17235],{},"Snapshot"," pour la déduplication, et de pouvoir accéder (à des fins d'archivage) directement aux sauvegardes sans contrainte.",[369,17238,17239,17241],{},[361,17240,17113],{}," permet également de compresser les sauvegardes (avec les bonnes options au montage).",[369,17243,17244,17245,17248,17249,178],{},"Comme ",[361,17246,17247],{},"RSync"," est utilisé, il est installé sur tous les clients (même windows), ce qui facilite son utilisation",[17250,17251,17252],"sup",{},[48,17253,1952],{"href":17254,"ariaDescribedBy":17255,"dataFootnoteRef":1645,"id":17257},"#user-content-fn-1",[17256],"footnote-label","user-content-fnref-1",[369,17259,17260,17261],{},"L'application possède une interface moderne (à mon goût)",[17250,17262,17263],{},[48,17264,1958],{"href":17265,"ariaDescribedBy":17266,"dataFootnoteRef":1645,"id":17267},"#user-content-fn-2",[17256],"user-content-fnref-2",[369,17269,17270],{},"L'application est contrôlable via une API Rest et via une API GraphQL.",[127,17272,17274],{"id":17273},"quelques-captures-décran","Quelques captures d'écran",[11,17276,17277,17281,17285],{},[120,17278],{"alt":17279,"src":17280},"Dashboard","\u002FWoodstock\u002Fdashboard.png",[120,17282],{"alt":17283,"src":17284},"Hosts","\u002FWoodstock\u002Fhosts.png",[120,17286],{"alt":17287,"src":17288},"RunningTask","\u002FWoodstock\u002Frunning_tasks_0.png",[127,17290,17292],{"id":17291},"comment-linstaller","Comment l'installer ?",[11,17294,17295,17296,17299],{},"J'ai écrit un site Internet pour présenter Woodstock: ",[48,17297,17193],{"href":17193,"rel":17298},[346]," et porter la documentation.",[11,17301,17302],{},"Vous pouvez retrouver les liens de téléchargement depuis le repository de code sources :",[505,17304,17305,17310],{},[369,17306,17307],{},[48,17308,1632],{"href":1632,"rel":17309},[346],[369,17311,17312],{},[48,17313,16868],{"href":16868,"rel":17314},[346],[11,17316,17317,17318,17323],{},"L'installation se fait très simplement, j'ai écrit un peu de ",[48,17319,17322],{"href":17320,"rel":17321},"https:\u002F\u002Fwoodstock.shadoware.org\u002Fdoc\u002Finstallation.html",[346],"documentation"," pour expliquer cela.",[11,17325,17326],{},"Il est possible d'effectuer une installation via:",[505,17328,17329,17332,17335],{},[369,17330,17331],{},"les sources",[369,17333,17334],{},"un paquet debian\u002Fubuntu",[369,17336,17337],{},"l'image docker (c'est l'installation que j'utilise moi-même)",[127,17339,17341],{"id":17340},"par-rapport-à-mon-besoin","Par rapport à mon besoin",[11,17343,17344],{},"Reprenons mes critères :",[505,17346,17347,17352,17355,17358,17361,17364],{},[369,17348,17349,17351],{},[361,17350,17045],{},": Check",[369,17353,17354],{},"Client léger: Check",[369,17356,17357],{},"Archivage sur disque dur USB à plat: Partiel (Btrfs me permet de le faire manuellement à l'aide d'un script maison)",[369,17359,17360],{},"Compatible Windows\u002FLinux: Check (via rsync)",[369,17362,17363],{},"Sauvegarde sans y penser (automatique): Check",[369,17365,17366],{},"API ou IHM simple d'utilisation: Milk Check. L'API est là. Il est simple d'utilisation pour moi, mais peut encore être amélioré",[11,17368,17369],{},"Donc pour l'instant il répond à mes besoins mais reste améliorable.",[127,17371,17373],{"id":17372},"roadmap","Roadmap",[505,17375,17376,17379,17382],{},[369,17377,17378],{},"Avoir un outil d'archivage automatique intégré.",[369,17380,17381],{},"Les suppressions automatiques de sauvegardes sont à prévoir également.",[369,17383,17384,17385],{},"Peut-être remplacer Btrfs par autre chose, car actuellement",[505,17386,17387,17390,17396,17399,17402],{},[369,17388,17389],{},"Sans Btrfs, le logiciel ne peut pas fonctionner, donc cela limite les OS du serveur de sauvegarde.",[369,17391,17392,17393,17395],{},"Il y aurait des problèmes de performances avec ",[361,17394,17113],{}," en cas de trop grand nombre de snapshots et de l'utilisation des qgroups. Pour l'instant je n'ai ce genre de problème que lors de la suppression de sauvegarde.",[369,17397,17398],{},"Il n'est pas possible de faire de la déduplication inter-machines simplement.",[369,17400,17401],{},"L'utilisation d'un système de fichier unique empêche l'utilisation de plusieurs serveurs de backups, synchronisés. (Je me dis que sur les grosses infrastructures, il peut-être intéressant de scaler horizontalement)",[369,17403,17404,17405],{},"Par contre, si je remplace Btrfs par autre chose, les questions sont alors",[505,17406,17407,17410,17413,17416],{},[369,17408,17409],{},"par quoi ?",[369,17411,17412],{},"Comment fais-je pour Rsync ?",[369,17414,17415],{},"Dois-je le remplacer aussi ?",[369,17417,17418],{},"Avoir mon propre client de sauvegarde (comme UrBackup) ?",[11,17420,17421],{},"Si vous avez des éléments de réponses, ou des idées là aussi, n'hésitez pas non plus à m'envoyer un mail.\nSi vous aussi n'êtes pas satisfaits de votre solution de sauvegardes, contactez-moi pour me dire ce qu'il vous faudrait, voir même pour contribuer.",[127,17423,17425],{"id":17424},"quelques-chiffres","Quelques chiffres",[11,17427,17428,17429,17431,17432,178],{},"À l'heure actuelle, je suis passé à ",[154,17430,16321],{}," pour mes sauvegardes en ayant migré toutes mes sauvegardes depuis ",[154,17433,1012],{},[11,17435,17436,17437,17439,17440,17442],{},"Je peux donc faire quelques comparaisons rapides (",[154,17438,16321],{}," méritant encore quelques évolutions pour atteindre le niveau de ",[154,17441,1012],{}," dans certains domaines).",[11,17444,17445],{},"Je possède 6 machines dont l'espace est répartit comme suite :",[21,17447,17448,17457],{},[24,17449,17450],{},[27,17451,17452,17454],{},[30,17453,566],{},[30,17455,17456],{},"Stockage",[40,17458,17459,17467,17475,17483,17491,17499],{},[27,17460,17461,17464],{},[45,17462,17463],{},"pc-windows",[45,17465,17466],{},"260.8 Gb ",[27,17468,17469,17472],{},[45,17470,17471],{},"pc-portable-1",[45,17473,17474],{},"148.1 Gb",[27,17476,17477,17480],{},[45,17478,17479],{},"pc-portable-2",[45,17481,17482],{},"41.0 Gb",[27,17484,17485,17488],{},[45,17486,17487],{},"pc-linux",[45,17489,17490],{},"153.3 Gb",[27,17492,17493,17496],{},[45,17494,17495],{},"nas",[45,17497,17498],{},"1.4 Tb",[27,17500,17501,17504],{},[45,17502,17503],{},"server-ovh",[45,17505,17506],{},"189.6 Gb",[11,17508,17509],{},"Certains fichiers (comme les photos de vacances, ...) peuvent être sur plusieurs machines (nas + pc fixe).",[21,17511,17512,17523],{},[24,17513,17514],{},[27,17515,17516,17519,17521],{},[30,17517,17518],{},"Comparaison",[30,17520,1012],{},[30,17522,1681],{},[40,17524,17525,17539,17550,17560,17571,17581,17592],{},[27,17526,17527,17530,17533],{},[45,17528,17529],{},"Pool de sauvegarde",[45,17531,17532],{},"le stockage optimisé 1,88 To",[45,17534,17535,17536,17538],{}," Le stockage ",[361,17537,16917],{}," avec compression prend 2,1 To",[27,17540,17541,17544,17547],{},[45,17542,17543],{},"Compression pc-windows",[45,17545,17546],{},"357 Go non compressé",[45,17548,17549],{},"326 Go non compressé",[27,17551,17552,17554,17557],{},[45,17553],{},[45,17555,17556],{},"246 Go compressé",[45,17558,17559],{},"245 Go compressé",[27,17561,17562,17565,17568],{},[45,17563,17564],{},"Compression pc-portable-1",[45,17566,17567],{},"74 Go non compressé",[45,17569,17570],{},"79 Go non compressé",[27,17572,17573,17575,17578],{},[45,17574],{},[45,17576,17577],{},"70 Go compressé",[45,17579,17580],{},"75 Go compressé",[27,17582,17583,17586,17589],{},[45,17584,17585],{},"Temps de sauvegarde incrémental NAS",[45,17587,17588],{}," 21 minutes en moyenne",[45,17590,17591],{},"15 minutes en moyenne",[27,17593,17594,17597,17600],{},[45,17595,17596],{},"Temps de sauvegarde incrémental pc-linux",[45,17598,17599],{},"30 minutes en moyenne",[45,17601,17602],{},"7 minutes en moyenne",[11,17604,17605],{},"J'ai l'impression que la notion de compress-force n'est pas prise en compte, et que la compression n'est pas des plus efficaces.",[11,17607,17608],{},"Pour la taille du pool de sauvegarde cela ne m'étonne pas vu que la déduplication n'est pas cross-machine.",[11,17610,17611],{},"Pour le temps de sauvegarde c'est une bonne surprise mais là aussi, je pense que c'est normal vu que rsync ne doit pas faire de déduplication à\nson niveau.",[17613,17614,17617,17622],"section",{"className":17615,"dataFootnotes":1645},[17616],"footnotes",[127,17618,17621],{"className":17619,"id":17256},[17620],"sr-only","Footnotes",[366,17623,17624,17635],{},[369,17625,17627,17628],{"id":17626},"user-content-fn-1","A voir si je conserve ce mode de fonctionnement pour le futur. J'envisage de peut-être prendre un client lourd. ",[48,17629,17634],{"href":17630,"ariaLabel":17631,"className":17632,"dataFootnoteBackref":1645},"#user-content-fnref-1","Back to reference 1",[17633],"data-footnote-backref","↩",[369,17636,17638,17639],{"id":17637},"user-content-fn-2","Mais si un UI\u002FUX souhaite améliorer l'interface, je suis preneur. ",[48,17640,17634],{"href":17641,"ariaLabel":17642,"className":17643,"dataFootnoteBackref":1645},"#user-content-fnref-2","Back to reference 2",[17633],{"title":1645,"searchDepth":1646,"depth":1646,"links":17645},[17646,17647,17652,17653,17654,17655,17656,17657,17658],{"id":16942,"depth":1646,"text":16943},{"id":17033,"depth":1646,"text":17034,"children":17648},[17649,17650,17651],{"id":16228,"depth":1651,"text":13740},{"id":16196,"depth":1651,"text":13789},{"id":17170,"depth":1651,"text":17171},{"id":17208,"depth":1646,"text":17209},{"id":17273,"depth":1646,"text":17274},{"id":17291,"depth":1646,"text":17292},{"id":17340,"depth":1646,"text":17341},{"id":17372,"depth":1646,"text":17373},{"id":17424,"depth":1646,"text":17425},{"id":17256,"depth":1646,"text":17621},{"type":8,"value":17660},[17661,17663,17669,17671,17673,17680,17686],[11,17662,13],{},[11,17664,16927,17665,16932,17667,178],{},[48,17666,16931],{"href":16930},[154,17668,16321],{},[16936,17670,16939],{"id":16938},[127,17672,16943],{"id":16942},[11,17674,16946,17675,16952],{},[154,17676,17677],{},[48,17678,1012],{"href":13731,"rel":17679},[346],[11,17681,17682,16957,17684,16960],{},[154,17683,1012],{},[361,17685,1062],{},[11,17687,16963],{},{"planet":1766},{"title":51,"description":13},"posts\u002FWoodstock\u002F2020-09-20_woodstock",[1772,1773,16917,982,6078,6079,1682],"3u7RFr1fMKcgbLywQbn4HX3gySdxxhd7EFlJL3Est34",1777582399249]