Objets et méthodes
Le contenu passe par les points d’accès de blobs JMAP standard (
/jmap/upload, /jmap/download) : une création de FileNode référence un blobId téléversé. FileNode déclare son vocabulaire d’écriture au répartiteur : une propriété qu’un client ne peut pas écrire est refusée nommément, jamais abandonnée en silence.
Le téléchargement
Un blob est immuable par construction (son identifiant est le SHA-256 du contenu), et la porte de téléchargement le dit./jmap/download répond ETag: "<blobId>" et Cache-Control: private, max-age=31536000, immutable (privé : la ressource est authentifiée, jamais pour un cache partagé), et un If-None-Match qui porte le bon tag reçoit 304 sans corps. Une requête Range: bytes= unique est honorée, 206 avec Content-Range, 416 si elle n’est pas satisfiable ; une spécification malformée ou multi-plages est ignorée et le 200 complet servi, comme la RFC 9110 le permet. Depuis ADR-145 le corps est lu en flux depuis le fichier lui-même, une fenêtre en mémoire à la fois, si bien qu’un gros fichier ne coûte au processus pas plus qu’un petit. La porte de partage publique (/share/{token}) honore le même Range mais garde Cache-Control: no-store : sa réponse compte dans un quota de téléchargements et ne doit jamais être resservie par un cache sans passer par le compteur.
L’historique de versions
Chaque mise à jour du contenu d’un nœud photographie le blob précédent dans son historique de versions, dans la même transaction que la mise à jour. Le contenu courant vit toujours sur le nœud lui-même ;FileVersion n’est que de l’histoire, et chacune de ses propriétés est immuable :
La capacité
urn:ietf:params:jmap:files annonce maxVersionsPerNode : la valeur effective du locataire, jamais une constante. C’est à la fois la fenêtre de conservation et la borne d’élagage : les versions au-delà des N plus récentes sont élaguées dans la transaction même qui a créé la plus récente, et leurs blobs sont libérés par le chemin de destruction à compteur de références, jamais par une suppression directe, puisqu’un blob peut être partagé par déduplication. Un client doit conditionner son entrée de menu « historique de versions » à la présence de cette clé, plutôt que de supposer que la surface existe.
La rétention est un réglage du locataire, appliqué à chaud : oximail tenant update --max-file-versions N (ou --clear-max-file-versions), 10 par défaut, plafond d’instance 100, et 0 désactive l’historique, ce qui court-circuite l’instantané au lieu de l’insérer puis de l’élaguer dans la même transaction, et libère les versions existantes au prochain enregistrement. Tenant/get sert maxFileVersionsPerNode comme valeur stockée (null pour le défaut, pour qu’une console distingue « défaut » de « réglé à 10 »), Tenant/set l’accepte, et un changement fait bouger sessionState, si bien qu’un client ouvert relit la session au lieu de garder l’ancienne annonce jusqu’à sa reconnexion. L’effet tombe au prochain enregistrement de chaque fichier ; il n’y a pas de verbe d’élagage immédiat.
FileVersion/queryexige un filtrefileNodeId. Les versions n’ont pas de sens en liste à l’échelle du compte, et exiger ce point d’ancrage garde la surface bornée par le plafond de conservation. Les plus récentes d’abord par défaut. SonqueryStateest l’état de la collection FileNode : les versions ne changent que quand le contenu d’un nœud change, ce qui fait avancer cet état, donc un seul curseur est cohérent pour les deux.canCalculateChangesvautfalse: il n’y a pas de journal de changements distinct pour les versions, donc re-interrogez sur un changement de FileNode.FileVersion/restorene détruit jamais l’histoire. L’opération passe par le même point de passage de stockage qu’une mise à jour de contenuFileNode/set { blobId }: le contenu sur lequel vous êtes est donc photographié comme nouvelle version avant d’être remplacé. La version d’où vous venez reste atteignable, et l’élagage s’applique comme d’habitude.sizeetcontentTypesont restaurés depuis l’enregistrement de version, qui fait foi puisqu’il les a capturés au moment de la photographie. Restaurer la version dont le blob est déjà le courant ne change rien au contenu, mais répond quand même en succès. La méthode accepte unifInStateoptionnel, pour le même contrôle de concurrence optimiste queFileNode/set, vérifié atomiquement avec l’écriture.- Les droits suivent le nœud. Lire la liste des versions demande
mayReadsur le nœud propriétaire,restoredemandemayWrite(un montage partagé en lecture seule répondforbidden). Un nœud qui n’existe pas et un nœud qu’un appelant partagé n’a pas le droit de lire répondent avec un libellé identique : la surface des versions n’est pas un oracle d’existence.
Les verrous : qui tient un fichier ouvert
Un fichier peut être tenu ouvert par un protocole qui a un « open », branché dans le même binaire par la couture du serveur (un moteur SMB, par exemple). La règle : le protocole qui a un « open » tient ; celui qui n’en a pas consulte. Le registre vit en mémoire, dans le processus, donc un verrou meurt avec la connexion ou le binaire, sans bail ni balayage. JMAP ne tient jamais rien :FileNode/set refuse forbidden toute mise à jour, renommage, mise à la corbeille ou destruction d’un nœud tenu, et FileVersion/restore refuse d’en remplacer le contenu, en nommant le porteur et l’âge du verrou (locked by alice@x.test via SMB (12 min), and N other open(s) s’ils sont plusieurs). FileNode/get sert lockedBy (String | null, l’étiquette et le protocole du porteur le plus ancien), lu en direct et hors de l’état /changes : un indicateur que le client affiche, la garantie étant le refus à l’écriture. Le grain est le nœud, pas la plage d’octets, puisqu’un rédacteur JMAP remplace le blob entier. Sans un tel protocole le registre est vide et la propriété vaut toujours null.
Les comptes miroirs
Un compte miroir est un compte partagé dont le Drive montre, en lecture seule, les dossiers qu’une organisation appairée partage avec ce serveur (fédération). Un client voit un compte partagé ordinaire et les méthodesFileNode ordinaires ; ce qui change, c’est où vit l’arborescence et qui peut l’écrire.
- L’arborescence est tenue en mémoire, copiée depuis le serveur du propriétaire et maintenue à
[federation] mirror_staleness_secsd’âge au plus (120 s par défaut) ; rien du propriétaire n’est stocké sur ce serveur. L’étatFileNode/changesd’un miroir vaut<époque>.<séquence>: un redémarrage crée une nouvelle époque, si bien qu’un état antérieur répondcannotCalculateChangeset que le client se resynchronise.FileNode/queryChangesrépond toujourscannotCalculateChanges: un miroir ne garde pas de journal de requêtes, le client relance sa requête. - Lecture seule pour tout appelant.
FileNode/setsur un compte miroir est refusé avecforbidden, dont la description commence parmirror-read-only, avant toute lecture ;myRightsn’accorde que la lecture. - Un état nommé, jamais un dossier vide. Tant que la copie est en cours, le propriétaire injoignable, l’accès révoqué ou l’appairage inactif, la liste porte un fichier dont le nom le dit (
(copy in progress from partner.example), …) à la place de l’arborescence. - Le téléchargement lit chez le propriétaire.
/jmap/downloadsert un fichier miroir en lisant son contenu sur le serveur du propriétaire, derrière un court cache en mémoire. Un propriétaire injoignable répond503avectype: "ownerUnreachable", un refus du propriétaire502avectype: "ownerRefused";RangeetIf-None-Matchse comportent comme plus haut. Un fichier miroir n’a pas de vignette (404,type: "noRendition").
Comportements à connaître
typeest respecté.type: "directory"crée un répertoire ; le champ est validé, jamais deviné d’après la présence d’un blob.- Le nom implique le type. Un fichier créé sans
contentTypereçoit le type MIME que son extension implique, par la même table que les autres portes ; un type explicite gagne toujours, et un nom qui ne dit rien laissenullplutôt qu’unapplication/octet-streamdeviné. ids: nullest borné.FileNode/getsans identifiants rend chaque nœud tant que leur nombre, compté sur ce que l’appelant recevrait, ne dépasse pasmaxObjectsInGet; au-delà, l’appel répondrequestTooLarge(RFC 8620 §5.1), commeEmail/get, et un client pagine avecFileNode/query(un filtreparentId, par exemple) puisFileNode/getsur ses identifiants. Les lectures qu’il y a derrière procèdent par lots de 500, si bien qu’un Drive de dizaines de milliers de nœuds ne tombe plus sur une limite de paramètres de la base.- Un nom que Windows réserve à un périphérique est refusé.
CON,PRN,AUX,NUL,COM0àCOM9etLPT0àLPT9, quelle que soit la casse et avec ou sans extension (aux.txt,nul.tar.gz), sont refusés eninvalidPropertiesà la création et au renommage : servi tel quel sur un partage de fichiers, un tel nœud ne pouvait être ni ouvert, ni renommé, ni supprimé depuis l’Explorateur Windows. Les autres caractères que Windows refuse (< > : " | ? *, un point ou une espace final) restent permis ; les traduire revient à la face de partage de fichiers. - Les attributs Windows sont conservés. Un protocole de partage de fichiers branché dans le binaire (un moteur SMB, par exemple) peut déclarer lecture seule, caché, système et archive sur un nœud ; le serveur les garde tels que déclarés, hors du fil JMAP (une notion Windows dont aucun client JMAP n’a besoin), et n’en applique aucun lui-même. Tout changement du contenu d’un fichier, par n’importe quelle porte (
FileNode/set, une version restaurée, un partage de fichiers), repose l’attribut archive, pour que la prochaine sauvegarde incrémentale d’un outil Windows reprenne le fichier. - L’arbre a une racine ; le fil utilise
null. Chaque compte a une racine de stockage ;parentId: nulldans une requête signifie « premier niveau ». Les déplacements de répertoires sont vérifiés contre les cycles : un déplacement qui créerait un cycle de parenté est rejeté. - La corbeille voyage dans la même transaction. Une mise à jour d’
isTrashedest atomique avec le reste de la mise à jour (la mise à la corbeille récursive d’un répertoire est une seule transaction), et les nœuds à la corbeille se restaurent depuis la corbeille visible (ADR-035). Le balayage des corbeilles détruit ce qui est à la corbeille depuis plus longtemps que l’horizon du locataire, sa surchargedrive.trash_retention_daysou à défaut[drive] trash_retention_days(30 par défaut, plancher 1 ; une surcharge à0ne purge rien, voir les horizons par locataire), par la même sortie qu’oximail drive purge-trash: un compte dont un message est sous séquestre garde aussi ses fichiers, et chaque nœud meurt dans une transaction (blob libéré par l’outbox de destruction, ligne, versions, liens de partage) avec un changementFileNodeDestroyedannoncé, si bien que la corbeille d’un client synchronisé ne garde jamais ce que le serveur a retiré (stockage). - Les favoris sont par lecteur. Mettre en favori un fichier partagé marque votre vue ; l’objet du propriétaire n’est pas touché.
- Un droit absent vaut « non ». Un nœud partagé
{mayRead, mayWrite}ne donne aucunmayDelete: une clé absente d’un grant stocké se lit fausse, pour chaque bit, dans chaque vocabulaire. La règle de lecture qui remontait les partages en écriture sans la clé laissait un bénéficiaire mettre à la corbeille la racine même du partage, dans le drive du propriétaire. Les droits sur un nœud s’accordent aussi depuis la CLI,oximail drive share --path Partages/Scan --grantee-email … --rights mayRead,mayWrite, par la même chaîne de validation queFileNode/setet avec la mêmeShareNotificationau bénéficiaire ; cette notification porteobjectSubtype(directoryoufile), si bien qu’un partage retiré, dont le bénéficiaire ne peut plus résoudre le nœud, est encore nommé pour ce qu’il était (CLI). - Le quota est débité de la croissance nette exacte. Une mise à jour de contenu est débitée de la nouvelle taille, plus la version stockée de l’ancien contenu, moins ce que la rétention élague : mesuré avant et après les écritures, et une croissance qui franchit le quota du Drive est refusée
overQuotaavec l’objet entier annulé. Un patch qui ne fait pas grossir le Drive (un fichier plus petit, un renommage) n’est jamais refusé ; un nouveau fichier est refusé avant que sa ligne existe (ADR-151, quotas). - Les vignettes sont dérivées côté serveur pour les types prévisualisables ; les clients n’envoient pas les leurs.
- Les liens de partage sont bornés et comptés. Un
FileShareLinkdonne accès à exactement le nœud qu’il nomme, appliquemaxDownloadsau téléchargement près, et un échec après que le compteur a bougé (un blob manquant, une plage non satisfiable) rend l’unité, si bien qu’une sonde ne brûle jamais le quota du destinataire. Un lien sur la racine interne du compte répond comme un nœud inexistant, et un jeton malformé alimente fail2ban : le générateur n’a jamais frappé que 32 caractères alphanumériques, tout le reste est une signature de sondage. Le blob servi est celui que le nœud référence, résolu dans une seule jointure avec le lien ; un lecteur anonyme n’atteint rien d’autre.