PiApplications 2026
Mise à jour du 05/06/2026.

Spécifications techniques de l'archiveur

L'archiveur est le système qui enregistre de façon systématique tout nouveau message reçu du lecteur par l'intermédiaire de la file d'attente des messages lus. Il est aussi interrogé par le lecteur pour savoir si le code SHA256 d'un jeu d'en-têtes de message lui est déjà connu. Si ce code est inconnu, alors le lecteur considère le message comme nouveau.

Modèlisation des données.

Nous allons devoir stocker un grand nombre de données pour un temps relativement long (généralement 5 ans). Un système traditionnel de stockage de fichiers est trop fragile et vulnérable pour servir de support à l'archivage. En conséquence, nous décidons d'utiliser une base de données et notre choix s'est porté sur PostgreSQL en raison de son portage multi-OS, de son efficacité, de sa légéreté ainsi que de son origine "libre" (pas de coût de licences). Le suivi des interrogations SQL dans une application importante est difficile et nuit à la simplicité de maintenance. Nous allons simplifier ce point via la règle qui suit :

L'archiveur, avec l'émetteur est le seul bloc fonctionnel à accéder à la base de données et tout accès à cette base se fait via un slot (exécuté par le thread d'affinité de l'objet archiveur).

Pour concevoir notre modèle de données, nous devons tenir compte des besoins du système pimail mais pas seulement.

L'archiveur vu du lecteur.

Le stockage des code SHA256 concerne aussi bien les courriels ré-émis que ceux ignorés qu'elle qu'en soit la raison. Le code de hachage des en-têtes est maintenu dans la colonne c_hdrcrc de table t_archive via l'archiveur. Cette table contient tous les codes de hachage des en-têtes que les courriels correspondant aient été définitvement archivés ou non. Une tâche particulière de PostgreSQL (pg_cron) parcours régulièrement cette table pour éliminer les archives "anciennes" non validées (Encore à faire). Une archive est "ancienne" lorsqu'elle n'a pas été validée et qu'elle est enregistrée depuis une date antérieure à la date courante diminuée de la période de rétention fixée par la configuration. La requête SQL qui permet cette recherche est :

-- recherche
SELECT c_id FROM pimail.t_archive WHERE c_received < CURRENT_TIMESTAMP - INTERVAL '%1 days';
-- suppression
DELETE FROM pimail.t_archive WHERE c_received < CURRENT_TIMESTAMP - INTERVAL '%1 days';

Ici, %1 doit être remplacé par le nombre de jours au-delà l'archive est considérée ancienne.

L'enregistrement des courriels produits par le lecteur se fait en deux temps :

  1. Enregistrement du flux EML et du code SHA256 des en-têtes lues par le lecteur dans le dossier source. Un indicateur précise que cet enregistrement n'est pas validé (t_archive.c_validated).
  2. A la création du tuple, la colonne t_archive.c_validated vaut false (et non NULL). La validation, qui ne peut être effectuée que par le composeur, consiste à passer cet indicateur à true.

La requête de validation lorsque l'on dispose de la clef primaire du courrile dans la table t_archive est :

-- validation archivage
UPDATE pimail.t_archive SET c_validated=TRUE WHERE c_id=%1

Les courriels lus sont enregistrés dans la table t_archive. Il est impératif de pouvoir réassocier les courriels à leur dossier source. Les dossiers source doivent donc être enregistrées en base de données de façon à pouvoir être liées à leurs courriels. Si on déclare les dossier source utilisés par le lecteur dans un fichier de configuration, ce dernier devra être synchronisé avec les dossiers source en base de données. Cette synchronisation peut être une source importante d'erreurs. Pour l'éviter, nous déclarons les dossiers source uniquement en base de données. Le lecteur prend donc les informations de connexion aux dossiers source dans la base de données (via l'archiveur) et non dans un fichier de configuration. La requête qui retourne les éléments de connexion y compris le dossier est une jointure sur colonne :

-- lecture des informations de tous les dossiers source
SELECT * FROM pimail.t_mailbox mbx JOIN pimail.t_folder fld ON fld.c_mailbox=mbx.c_id

L'archiveur vu du redirecteur.

Le rôle du redirecteur est de ré-expédier les messages depuis un dossier "source" vers une ou plusieurs BAL externes aussi bien pour action que pour information ou encore en copie cachée. Son fonctionnement repose sur la table de redirection du type :

Table de redirection.

C'est le rôle de la table t_mapping et de sa clef étrangère fk_oldfrom sur la colonne c_oldfrom. Cette clef étrangère permet de rediriger la colonne t_mailbox.c_from vers t_mapping.c_newfrom en précisant éventuellement les BAL en copie et en copie cachée.

La requête qui permet de connaitre les élement de redircetion est :

-- élément de redirection
SELECT * FROM pimail.t_mapping WHERE c_oldfrom IN (SELECT c_id FROM pimail.t_mailbox WHERE c_email='%1')

Ici %1 doit être remplacé par l'adresse de la BAL du dossier source.

L'archiveur vu du composeur/chiffreur.

Le composeur/chiffreur est le dernier bloc fonctionnel avant ré-expédition d'un message. Cet ensemble de deux blocs peut être ramené en un seul nommé rédacteur. Si le rédacteur échoue dans le traitement du courriel reçu du système de filtrage, il ne valide pas l'enregistrement dans la table t_archive.

S'il peut traiter le courriel reçu du système de filtrage, il ajoute en fin de message une bannière avec l'adresssage initial : From, Sender (si plusieurs From), To, Cc, Bcc, Message-ID et Subject. Cela permet au destinataire de retrouver l'adressage d'origine (avant réexpédition). La mention de l'objet est particulièrement importante avec S/MIME puisque ce dernier est remplacé par un objet générique non porteur d'information (S/MIME ne chiffre pas l'objet car il est ans une en-tête).

Une fois le message pour réexpédition composé et éventuellement chiffré, il est stocké dans la file d'attente des courriels redirigés. La file d'attente sollicite alors l'archiveur pour que le message soit enregistré dans la table t_redirected mais en laissant la colonne c_sent à NULL. C'est l'émetteur qui, une fois l'envoi effectué, demande à l'archiveur de fixer la date d'envoi dans cette colonne. L'intérêt de cette approche est la reprise sur incident.

L'émetteur, quant à lui, n'est pas sollicité par la file d'attente. Il intègre un mécanisme de reprise sur incident également utilisé en mode nominal. Il dispose d'un chronomètre dont le signal périodique est capturé par un slot. Ce slot effectue une scrutation de la table t_redirected à la recherche des colonnes c_sent égale à NULL. Dès qu'il en trouve une, il récupère le message stocké sous forme de BLOB et tente de l'envoyer.

En cas de crash de l'application ou de chute réseau, les messages non encore émis sont ainsi automatiquement réexpédier. Pour éviter une réexpédition infinie (serveur cible inexistant par exemple), la table t_redirected dispose de la colonne c_try. La configuration fixe la période de scrutation et le nombre maximum d'essai d'envoi (souvent fonction de cette période). Lorsque le nombre maximum d'essai est dépassé, le réexpédition est ignorée et la première fois ou cette réexpédition est ignorée, une trace est enregistrée tandis que la colonne c_signaled passe de FALSE (valeur par défaut à la création) à TRUE. Pour forcer la rexpédition d'un courriel, il suffit de mettre c_try à 0 et de remettre c_signaled à FALSE.

Pour permettre à l'archiveur et à l'émetteur d'accèder depuis des threads différents au même gestionnaire de connexion, ce dernier est intégré à la classe singleton PgDatabse. Ce singleton peut donc être invoqué par les deux threads. Chacun peut alors acquérir, utiliser et restituer leur propre connexion y compris au même instant.

L'archiveur vu d'un système externe.

La base de données ne se limite pas à stocker les messages reçus et émis. Elle contient des informations très utiles pour un grand nombre d'autres applications à condition de l'aménager.

Pour cela, il faut être en mesure de rattacher chaque courriel archivé à son dossier "source". Cela doit aussi permettre de recenser les contacts. On doit aussi pouvoir extraire les PJ, les passer à l'OCR et indexer leur contenu pour des recherches ultérieures. Toutes ces fonctions sont en dehors du périmètre de notre application de redirection mais le modèle de données doit les favoriser.

Bien que cela ne soit pas nécessaire à l'application, c'est l'archiveur qui sera chargé de l'extaction puis de l'enregistrement des PJ car il reçoit le flux EML complet qui intègre notamment les PJ. Le processus de suppression des archives "anciennes" doit également supprimer les PJ des archives à supprimer. Cela se fait automatiquement grâce à la directive ON DELETE CASCADE placée sur la clef étangère de la colonne c_archive.

Fonctionnement de l'archiveur.

Vis-à-vis de l'application dans son ensemble, l'archiveur délivre plusieurs services :

Requête de recherche d'un code SHA256 d'en-têtes de courriel.

Il s'agit pour l'archiveur d'exécuter un requête SQL simple sur la table t_archive. Toutefois, comme les slots de l'archiveur fonctionnent sur leur propre thread on évitera l'appel direct de méthode qui s'exécuterait sur le thread principal. La question sera posée par le lecteur via un signal requestForHeadersHashCode(const QString& hashCode). Ce signal est traité par un slot qui exécute la requête (sur le thread d'affinité de l'objet archiveur) et répond via le signal responseForHeadersHashCode(const QString& hashCode, bool found). Le lecteur devra disposer d'une connexion via un slot à ce signal pour connaitre la réponse de l'archiveur.

Energistrement d'un flux EML.

Les files d'attentes émettent le signal directoryUpdated auquel l'archiveur en tant que consommateur est connecté. L'archiveur émet alors le signal requestForFiles vers la file d'attente concernée. Il reçoit en réponse la liste des fichiers de la file d'attente à traiter. Il récupère chaque fichier de cette liste pour l'enregistrer avec ses métadonnée, ses PJ et leur métadonnées au sein d'une mêm transaction. Après cet enregistrement, il émet le signal consumed pour indiquer à la file d'attente qu'elle peut supprimer ce fichier des prochains à lui envoyer.

Validation de l'enregistrement d'un courriel.

A sa création, un enregistrement n'est jamais validé : cette capacité est interdite à l'archiveur ainsi qu'aux autres blocs fonctionnel à l'exception du rédacteur. Lorsqu'il reçoit une demande de validation de ce dernier, l'archiveur place le booléen (colonne t_archive::c_validated) à true.

Les enregistrements non validés sont conservés durant un temps donné fixé (en nombre de semaines) par voie de configuration. Au-delà de cette durée, ils sont automatiquement élmiminés par une tâche planifiée du SGBDR (pg_cron). Cela permet pendant la durée de rétention de pouvoir forcer la validation de l'enregistrement en cas de faux positif issu de la chaîne de traitement. Si le délai de conservation est inférieur ou égal à 0, les enregistrements non validés sont conservés indéfiniment.

Comme les slots de l'archiveur fonctionnent sur un thread dédié, les demandes de validation se font par la voie du signal emailValidated(const QString& crc) émis par le rédacteur et auquel l'archiveur est connecté.