Mirroring avec 4D 2004 (à partir de 4D 2004.3)

| Kent D. WILBUR | traduit de l'anglais par Stéphanie Tirtiat |

Le « mirroring » était intégré à 4D Backup dans les versions antérieures à 4D 2004. 4D Backup fut ensuite intégré dans 4D 2004, mais le mirroring n'était pas implémenté dans la première version. 4D 2004.3 propose maintenant deux nouvelles commandes qui permettent de créer un système de base miroir avec 4D Server. Cette note technique porte sur l'utilisation de ces deux commandes et explique comment configurer et installer un système de mirroring.

NDT : Les fonctionnalités permettant le mirroring ont été rajoutées en 4D 2004.2, mais certains dysfonctionnements concernant la gestion du fichier d'historique ayant été corrigés en 4D 2004.5, nous vous conseillons l'utilisation de cette dernière version. ♪

- Titre : Mirroring avec 4D 2004 (à partir de 4D 2004.3)

- Auteurs : Kent D. WILBUR, traduit de l'anglais par Stéphanie Tirtiat

- Parution : 1 juin 2006

- Licence : Ce document est issu de https://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- Introduction

- Qu'est-ce que le mirroring ?

- La base miroir requiert-elle des conditions spéciales ?

- Les fichiers d'historique

- Schéma séquentiel des fichiers d'historique

- Nouvelle commande « Nouveau fichier historique »

- Transfert du segment du fichier d'historique

- Nouvelle commande « INTEGRER FICHIER HISTORIQUE »

- Gestion d'erreur

- La base exemple

- Créer la base miroir donnée en exemple

- Les préférences du mirroring

- Paramétrage des préférences du miroir

- Côté serveur

- Côté client

- Le process du mirroring

- Côté serveur principal

- Côté serveur miroir

- Maintenir les utilisateurs hors de la base miroir

- Restauration d'urgence

- Catastrophe mineure

- Catastrophe majeure

- La fonction Nouveau fichier historique et opérations critiques

- Conclusion

- Télécharger la base exemple

Introduction

Le « mirroring » était intégré à 4D Backup dans les versions antérieures à 4D 2004. 4D Backup fut ensuite intégré dans 4D 2004, mais le mirroring n'avait pas été implémenté dans la première version. 4D 2004.3 propose maintenant deux nouvelles commandes qui permettent de créer un système de base miroir avec 4D Server. Cette note technique porte sur l'utilisation de ces deux commandes et explique comment configurer et installer un système de mirroring. L'objectif de cette note technique est de vous permettre de construire une base miroir avec ces deux commandes. Elle ne cherche pas à vous donner la solution parfaite : vous devrez certainement modifier le code proposé ici pour qu'il convienne à vos besoins spécifiques. Pour montrer efficacement le principe du mirroring, j'ai dû ajouter quelques techniques intéressantes comme la création et la maintenance de fichiers de préférences au format XML, ou la modification des préférences sur le serveur depuis un 4D Client, ce qui requiert une communication adaptée en Client/Serveur.

Qu'est-ce que le mirroring ?

Le mirroring est le fait d'avoir une seconde base identique tournant en parallèle de la base de production. L'objectif est de fournir le plus rapidement possible une solution de secours dans le cas où la base de production est inaccessible ou inopérante pour quelque raison que ce soit. Le mirroring fonctionnant sur la synchronisation entre les deux bases, cette base miroir sera aussi à jour, à partir de la dernière synchronisation, que la base de production qu'elle doit seconder. Si le fichier d'historique qui contient toutes les informations depuis la dernière synchronisation avec le serveur principal est récupérable, il suffira de synchroniser les dernières données saisies pour avoir une base miroir à jour et tournant comme serveur principal. Puisque la base miroir tourne déjà elle-même en serveur, tout ce processus peut être fait rapidement en quelques minutes pour rétablir une base à nouveau opérationnelle. Pas besoin de restaurer depuis un backup. Le mirroring est le moyen le plus rapide pour mettre en place un serveur de secours comme serveur principal.

La base miroir requiert-elle des conditions spéciales ?

Avant 4D 2004, la réponse était oui. Les machines impliquées devaient être pratiquement identiques : même partition de disque dur, même système d'exploitation. Elles devaient être littéralement la copie conforme. Avec 4D 2004, ce n'est plus nécessaire. Le mirroring de 4D 2004.3 peut même s'utiliser en cross-plateforme. Le seul impératif de la machine miroir est qu'elle ne peut absolument pas modifier les données elle-même. Vous devez donc vous assurer que votre méthode lancée au démarrage ne modifie aucune donnée automatiquement et qu'aucune personne ne peut accidentellement se connecter à cette base miroir et modifier quoi que ce soit.

Les fichiers d'historique

Le cœur des deux systèmes de mirroring, celui avant 4D 2004 et l'actuel, sont les fichiers d'historiques. Pour installer une base miroir, vous faites simplement une sauvegarde de votre base en intégrant un fichier d'historique. Quittez le serveur et transférez tous les fichiers vers la machine miroir. Tous les fichiers doivent être identiques. Les deux serveurs, le principal et le miroir sont alors relancés. La maintenance du serveur miroir se passe comme suit : périodiquement, un nouveau fichier d'historique est créé sur le serveur principal. Le fichier d'historique précédent est alors envoyé au serveur miroir où il est intégré. Ce processus prend effet indéfiniment pour maintenir une synchronisation entre les deux serveurs.

Schéma séquentiel des fichiers d'historique

Chaque sauvegarde et fichier d'historique d'un backup possède un numéro unique. Si vous avez l'habitude d'utiliser le backup de 4D Serveur, vous les reconnaîtrez facilement. Par exemple : MaBase[0739].4BK et MaBase[0738].4BL. Quand un système de mirroring est impliqué, chaque transfert d'un fichier d'historique vers la base miroir crée un nouveau segment du fichier d'historique. Pour les identifier, leur nom comporte une numérotation pour les segments :

MaBase[0739-0001].4BL ; MaBase[0739-0002].4BL ; MaBase[0739-0003].4BL ; etc.

Ce sont ces segments qui sont envoyés à la machine miroir pour y être intégrés.

Nouvelle commande « Nouveau fichier historique »

Nouveau fichier historique

La commande Nouveau fichier historique ferme le fichier d'historique courant, le renomme, et en crée un nouveau au même endroit que le précédent. C'est le même procédé utilisé par la commande SAUVEGARDER. La différence est qu'il n'y a pas de sauvegarde, seulement un fichier d'historique. La commande Nouveau fichier historique retourne le nom complet du chemin d'accès au fichier d'historique précédent qui vient d'être fermé et renommé. Ce nom contient le numéro de la dernière sauvegarde et le numéro de segment comme décrit précédemment. La commande ne s'exécute que sur un 4D Serveur. Si elle est exécutée sur une autre application, elle retourne une erreur 1412 et ne fait rien. Elle requiert aussi la présence d'un fichier d'historique. S'il n'y en a pas, elle retourne une erreur 1413. Tous les process du 4D Serveur sont arrêtés quand la commande Nouveau fichier historique est exécutée.

Transfert du segment du fichier d'historique

Une fois qu'un segment du fichier d'historique a été créé et fermé, il est transmis au serveur miroir. La façon dont le transfert s'opère dépend du développeur. Le fichier peut être transmis manuellement ou par programmation en utilisant des volumes partagés en réseau. Avec les 4D Internet Commands, vous pouvez créer un mécanisme de transfert de fichier par FTP, puis utiliser 4D Open pour que la base miroir intègre le fichier d'historique transféré par FTP. Dans la base exemple fournie, j'ai choisi d'utiliser les web services (SOAP) pour envoyer et intégrer les fichiers parce que c'est la solution la plus simple et la plus facile pour communiquer au serveur principal le statut des opérations d'intégration de fichier dans le cas d'un bon déroulement ou d'une erreur. Les fichiers de sauvegarde et d'historique se voient donc octroyer un numéro unique par la commande SAUVEGARDER.

Rappel : MaBase[0739].4BK et MaBase[0738].4BL. La différence ici est qu'il n'y a pas de sauvegarde, seulement un nouveau fichier d'historique.

Nouvelle commande « INTEGRER FICHIER HISTORIQUE »

INTEGRER FICHIER HISTORIQUE

La commande INTEGRER FICHIER HISTORIQUE lance l'intégration du fichier passé en paramètre dans la commande. La commande ferme alors le fichier d'historique existant, lit les dernières données entrées dans le fichier d'historique et les intègre dans le fichier de données. Le fichier intégré devient alors le fichier d'historique courant de la base. Le mirroring est maintenant terminé et les deux bases de données sont synchronisées. Cependant, plusieurs problèmes peuvent être soulevés durant le processus. Tout d'abord, la commande ne fonctionne qu'avec 4D Serveur. Si ce n'est pas un 4D Serveur, elle retourne une erreur 1412 et le fichier n'est pas intégré. Ensuite, il faut qu'il y ait un fichier d'historique courant renseigné, sinon la commande retourne une erreur 1403. Ces deux problèmes peuvent être facilement résolus. Mais le plus important est la désynchronisation du fichier d'historique. Les fichiers de sauvegarde et d'historique comportent en interne des suites numériques que j'appellerai, faute de trouver un meilleur terme, vérification de séquence numérique. Pour qu'une intégration se fasse correctement, les segments des fichiers de sauvegarde et d'historique doivent comporter une séquence numérique correcte. Vous ne pouvez pas changer cette séquence en renommant les fichiers. Ces nombres sont intégrés dans la structure des fichiers eux-mêmes et assurent que le prochain fichier à intégrer est bien le « suivant » correspondant à la « sauvegarde ». Si quelque chose vient perturber cette séquence numérique dans le fichier d'historique, l'intégration ne peut pas se faire et une erreur 1410 est générée qui signifie en gros « Mauvais fichier d'historique. Le fichier d'historique existe, mais ne correspond pas à la base de données en cours. »

Gestion d'erreur

Pour installer et faire fonctionner correctement un système de miroir, il faut pouvoir gérer les erreurs. Des problèmes peuvent parfois apparaître. Certains sans conséquence et le mirroring continue de fonctionner selon le « scheduler ». D'autres sont fatals. Peu importe l'erreur, il est essentiel de la gérer efficacement. Si vous n‘utilisez pas une procédure lancée par la commande APPELER SUR ERREUR et qu'une erreur se produit, le mirroring va s'arrêter sans avoir terminé la tâche, sans avoir rien fait ou encore sans même notifier qu'il y a eu une erreur. De plus si le process travaille avec un délai, il sera interrompu et ne se déclenchera plus. Il est donc primordial de créer et d'utiliser une routine avec APPELER SUR ERREUR quand on utilise une commande de mirroring.

La base exemple

La base donne l'exemple d'un simple système de mirroring. Il vous faudra deux serveurs 4D 2004.2 ou version ultérieure. La base contient des configurations paramétrées pour la planification du mirroring, un transfert et une intégration du fichier d'historique, une gestion d'erreur et une solution à une situation catastrophe.

Créer la base miroir donnée en exemple

Préalablement au mirroring, vous devez avoir créé des fichiers d'historique. Vous copiez ensuite la base de données sur une machine miroir. Suivez ensuite ces étapes exactement comme elles sont écrites :

1. Lancez la base de données. (Cette base de données utilise des préférences de configuration ; elles doivent être sélectionnées quand la base est lancée pour la première fois. Pour cette base de données, choisissez la configuration « Base principale ») ;

2. Lancez une sauvegarde des données. (Note : ce serait une bonne idée d'inclure le dossier des plugins dans la sauvegarde.) ;

3. Allez dans les préférences de sauvegarde et créez un nouveau fichier d'historique ;

4. Fermez la base de données ;

5. Copiez tout, structure, fichiers de données, sauvegardes, fichiers d'historiques, etc. sur le second serveur ;

6. Dans le dossier « Preferences », localisez le dossier Mirror et supprimez-le ;

7. Lancez la base de données miroir avec 4D Server et cette fois, choisissez la configuration « Base Miroir ». La base miroir est maintenant prête ;

8. Lancez la base principale « Base principale » avec 4D Server. Chaque fois que le miroir ne fonctionne plus, vous devez recommencer et recréer la base miroir depuis le début, en commençant par la sauvegarde du fichier de données. Un système de sélection de configuration a été développé pour cette base exemple ; vous pouvez l'utiliser ou non. Les points qui concernent ces configurations peuvent donc être ignorés.

Les préférences du mirroring

J'ai pensé qu'il était important de différencier les deux bases de données. Puisqu'on ne peut pas utiliser un fichier de données pour distinguer les bases de données, cela casserait le mirroring, j'ai opté pour la solution d'un fichier de préférences externe. C'est un simple fichier au format XML qui stocke les préférences. Pour le serveur miroir, le seul paramétrage utilisé est celui qui le renseigne comme serveur Miroir. Chaque fois que le serveur miroir est recréé, supprimez simplement les préférences et choisissez l'option « Miroir » ; c'est tout ce dont la base de données miroir a besoin. D'un autre côté, le serveur principal fait tout le travail. Il a donc besoin de bien plus de paramétrages.

Pour des raisons de sécurité, la base exemple requiert la saisie du nom de la base de données miroir. J'utilise ce nom en interne pour m'assurer que la base miroir est en fonctionnement et qu'elle correspond bien à celle à laquelle vous essayez d'envoyer le log, cela avant d'appeler la fonction Nouveau fichier historique.

Je ne veux pas créer un nouveau fichier d'historique pour rien. Le second paramétrage nécessaire est l'adresse IP du serveur miroir. Si vous préférez, vous pouvez utiliser le nom de domaine retrouvé à partir d'un DNS Lookup. Ensuite, c'est la planification du mirroring, en heures et minutes. Si vous sélectionnez 00 heure et 00 minute, le mirroring du serveur s'arrêtera. Dans un système miroir, vous devez utiliser un mécanisme de planification. Peut-être voudriez-vous en utiliser un qui se déclencherait à certains moments de la journée, comme l'automatisation de la sauvegarde par le système de backup intégré à 4D Serveur. Dans ce cas, je vous laisse le construire vous-même. L'indication du dernier transfert de log est purement informative. Elle ne peut pas être modifiée. Elle donne le dernier log qui a été intégré au serveur miroir avec succès. Cette information peut aider en cas de restitution d'urgence. La partie suivante des paramètres concerne le serveur SMTP et l'adresse email où seront envoyés les messages d'erreur. Si vous laissez le paramétrage par défaut (« Aucun »), aucun message ne sera envoyé. Pour terminer, il y a un indicateur de statut du mirroring du serveur en bas de la fenêtre de dialogue. S'il n'est pas en cours de fonctionnement, vous pouvez sélectionner la boîte à cocher pour lancer le processus de mirroring sur le serveur. Il démarrera si vous avez déterminé une planification (intervalle de temps). Si le mirroring est déjà en cours, il sera mis à jour avec les nouvelles valeurs.

Paramétrage des préférences du miroir

Côté serveur

Les préférences du miroir sont sauvegardées et utilisées sur le serveur. Puisque 4D crée déjà un dossier de préférences pour la sauvegarde et autres paramétrages, la base de données utilisera la même architecture pour créer ses propres préférences et les sauvegarder dans un fichier au format XML. Déterminons l'emplacement du fichier : ceci est fait en une simple méthode. Si vous souhaitez modifier l'emplacement, la chaine trouvée ici est simplement modifiée en fonction de l'emplacement où vous voulez que soit sauvegardé le fichier. Le code convertira le chemin Windows vers Mac si nécessaire.

Si (Faux)

` Methode: Miroir_tCheminMiroir
` Note technique : mirroring avec 4D 2004
` Créée par: Kent Wilbur
` Date: 9/27/2005
` But: Contient l'emplacement de la base miroir
¾f_Version2004x2:=Vrai
¾fK_Wilbur:=Vrai
Fin de si
` Declarer les parametres
C_TEXTE($0)
$0:="Preferences\\Mirror\\Settings.xml"
`Fin de méthode

Charger/Créer le fichier des préférences : quand le 4D Serveur est lancé, le processus de mirroring tente de charger et de lire le fichier de préférences en utilisant la méthode Miroir_GestPrefsMiroir. Toutes les manipulations de ce fichier de préférences s'opèrent dans cette seule méthode récursive Miroir_GestPrefsMiroir  ; il se peut donc qu'elle soit un peu compliquée à lire au premier abord. Essayez avant tout de suivre le code dans la méthode ci-dessous, en partant du principe que le fichier et dossiers de préférences n'existent pas, en examinant la partie du Au cas où où $tAction = « Load » (charger) - Voir page 9 -. Suivre le code pour le cas « Load » (charger) nous mènera à appeler à nouveau la méthode pour le cas « Create» (créer) ; retournez simplement en haut de la méthode et suivez maintenant ce second passage où la valeur $tAction = « Create » (créer).

Méthode : Miroir_GestPrefsMiroir

Si (Faux)
` Note technique : mirroring avec 4D 2004
` Créée par: Kent Wilbur
` But: créer un fichier de preferences pour le miroir
` Parametres
`   $1   -   Action
¾f_Version2004x2:=Vrai
¾fK_Wilbur:=Vrai
Fin de si
` Declarer les parametres
C_TEXTE($1;$tAction)
` Declarer les variables locales
C_BOOLEEN($fAbort)
C_ALPHA(16;$sXML_Reference)
C_ALPHA(16;$sXML_ElementReference)
C_ENTIER LONG($LProcessID)
C_TEXTE($tMirror_TimeInterval)
C_TEXTE($tPreferencesPath)
C_TEXTE($tSettingsFolderPath)
` Reassigner pour lisibilité
$tAction:=$1
` Declarer les valeurs par défaut
$fAbort:=Faux
$tPreferencesPath:=GEN_tFormatChemin (Miroir_tCheminMiroir )

Après avoir récupéré le chemin d'accès à partir de Miroir_tCheminMiroir, la méthode GEN_tFormatChemin modifie simplement le nom du chemin selon la plateforme (Macintosh ou Windows).

Au cas ou
: ($tAction="Create")  `créer
CONFIRMER("Sélectionnez le type de ce serveur.";"Base principale";"Base miroir")
Si (OK=1)
¾tMirror_ServerType:="Base principale"
Sinon
¾tMirror_ServerType:="Base miroir"
Fin de si
¾tMirror_DatabaseName:="Aucun"
¾tMirror_ServerIPAddress:="0.0.0.0"
$tMirror_TimeInterval:="00:00:00"
¾tMirror_LastLogNumber:="[0000]"
¾tMirror_SMTPServer:=¾tMirror_DatabaseName
¾tMirror_ErrorEMailAccount:="nom.prenom@domaine.com"
¾tMirror_AuthenticationRequired:="Non"
¾tMirror_AuthenticationUserName:=¾tMirror_DatabaseName
¾tMirror_AuthenticationPassword:=¾tMirror_DatabaseName
: (Nombre de parametres#1)
$fAbort:=Vrai  ` pas assez de parametres
Sinon
$tMirror_TimeInterval:=Chaine(¾hMirror_TimeInterval;h mn s )
Fin de cas
Au cas ou
: ($fAbort)
: ($tAction="Load")  `charger
$tSettingsFolderPath:=GEN_tCheminDossier ($tPreferencesPath)

La méthode GEN_tCheminDossier sépare le nom du chemin des dossiers du nom du fichier lui-même.

Si (Tester chemin acces($tSettingsFolderPath)#0)  ` le dossier existe-t-il ?
GEN_PreparerChemin ($tSettingsFolderPath)
Fin de si

La fonction Tester chemin acces retournera 0 (zéro) si la valeur passée est un chemin de dossier. Comme nous testons si c'est un dossier, si une autre valeur est retournée, un ou plusieurs de ces dossiers n'existent pas. GEN_PreparerChemin est une méthode qui va créer tous les dossiers manquants selon le chemin retourné. Ci-dessous, nous vérifions à nouveau l'existence du fichier avec la fonction Tester chemin acces. Elle retourne 1 si la valeur passée est un fichier. En vérifiant le chemin d'accès complet, y compris le nom du fichier, si toute autre valeur que 1 est retournée, cela signifie que le fichier n'existe pas.

Si (Tester chemin acces($tPreferencesPath)#1)
` vérifier si un fichier valide existe

Nous appelons donc à nouveau la méthode Miroir_GestPrefsMiroir pour créer le fichier des préférences.

Miroir_GestPrefsMiroir ("Create")  ` sinon créer un fichier de preferences

Si le fichier existe, nous chargeons simplement les paramétrages :

Sinon
$sXML_Reference:=DOM Analyser source XML($tPreferencesPath)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ServerType")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_ServerType)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ServerName")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_DatabaseName)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ServerIPAddress")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_ServerIPAddress)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_TimeInterval")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;$tMirror_TimeInterval)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_LastBackupNumber")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_LastLogNumber)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorSMTPServer")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_SMTPServer)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorEMail")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_ErrorEMailAccount)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorEMailAuthentication")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_AuthenticationRequired)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorEMailUsername")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_AuthenticationUserName)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorEMailPassword")
DOM LIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_AuthenticationPassword)
DOM FERMER XML($sXML_Reference)
<>hMirror_TimeInterval:=Heure($tMirror_TimeInterval)
Fin de si

: ($tAction="Save")  `sauvegarder
$sXML_Reference:=DOM Analyser source XML($tPreferencesPath)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ServerType")
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_ServerType)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ServerName")
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_DatabaseName)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ServerIPAddress")
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_ServerIPAddress)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_TimeInterval")
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;$tMirror_TimeInterval)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_LastBackupNumber")

DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_LastLogNumber)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorSMTPServer")
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_SMTPServer)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorEMail")
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_ErrorEMailAccount)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorEMailAuthentication")
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_AuthenticationRequired)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorEMailUsername")
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_AuthenticationUserName)
$sXML_ElementReference:=DOM Chercher element XML($sXML_Reference;
"/Mirror/PreferenceSetting/Mirror_ErrorEMailPassword")
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_AuthenticationPassword)
DOM EXPORTER VERS FICHIER($sXML_Reference;$tPreferencesPath)
DOM FERMER XML($sXML_Reference)

: ($tAction="SaveFromClient") | ($tAction="Save&LaunchFromClient")
`sauvegarde à partir du client ou sauvegarge et lancement à partir du client
Miroir_GestPrefsMiroir ("Save")
Au cas ou
: ($tAction="Save&LaunchFromClient")  ` sauvegarge et lancement à partir du client
$LProcessID:=Nouveau process("P_MiroirProcess";32000;"MirroringProcess";*)
Sinon
$LProcessID:=Chercher process("MirroringProcess";*)
Si ($LProcessID>0)
ENDORMIR PROCESS($LProcessID;0)  `réveille le process pour mettre à jour les paramètres
Fin de si
Fin de cas

: ($tAction="Create")  `créer
$sXML_Reference:=DOM Creer ref XML("Mirror")
$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_ServerType"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_ServerType)
$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_ServerName"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_DatabaseName)
$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_ServerIPAddress"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_ServerIPAddress)
$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_TimeInterval"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;$tMirror_TimeInterval)
$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_LastBackupNumber"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_LastLogNumber)
$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_ErrorSMTPServer"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_SMTPServer)
$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_ErrorEMail"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_ErrorEMailAccount)
$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_ErrorEMailAuthentication"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_AuthenticationRequired)
DOM EXPORTER VERS FICHIER($sXML_Reference;$tPreferencesPath)
$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_ErrorEMailUsername"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_AuthenticationUserName)

$tXML_ElementValue:="/Mirror/PreferenceSetting/Mirror_ErrorEMailPassword"
$sXML_ElementReference:=DOM Creer element XML($sXML_Reference;$tXML_ElementValue)
DOM ECRIRE VALEUR ELEMENT XML($sXML_ElementReference;<>tMirror_AuthenticationPassword)
DOM EXPORTER VERS FICHIER($sXML_Reference;$tPreferencesPath)
DOM FERMER XML($sXML_Reference)
<>hMirror_TimeInterval:=Heure($tMirror_TimeInterval)
Fin de cas
`Fin de méthode

Si vous avez quelques soucis avec les notions de XML, vous pouvez vous pencher un peu sur le XML en cherchant, dans notre base de connaissance, les notes techniques concernant le XML. Nous utilisons la méthode DOM (Document Object Model) qui nous permet de construire une structure complète en mémoire. Une analogie très simplifiée entre les commandes DOM et les concepts de 4D vous est présentée ci-dessous :
· DOM Creer ref XML
crée une référence XML en mémoire, similaire au concept de la création d'un document 4D View en mémoire.
· DOM Creer element XML
crée une référence à un élément individuel en mémoire, similaire à un pointeur ou une variable. Mais une variable hiérarchique. Notez tous les « / » dans les valeurs d'élément.
· DOM Analyser source XML
charge entièrement la source XML en mémoire et analyse toute la structure XML en mémoire.
· DOM ECRIRE VALEUR ELEMENT XML
stocke une valeur spécifique dans un élément individuel. Pensez à cela comme si c'était $ptrVariable-> := « maValeur »
· DOM Chercher element XML
trouve un élément individuel spécifié dans la structure XML chargée en mémoire.
· DOM LIRE VALEUR ELEMENT XML
charge la valeur d'un élément individuel dans un objet 4D. Pensez à cela comme si c'était maVariable/monChamp :=$ptrVariable->
· DOM EXPORTER VERS FICHIER
écrit la totalité de la structure dans un fichier.
· DOM FERMER XML
libère la mémoire utilisée par la structure XML lue via les commandes DOM.

Le fichier qui résulte de tout cela ressemble à ceci :

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<Mirror>
<PreferenceSetting>
<Mirror_ServerType>Base principale</Mirror_ServerType>
<Mirror_ServerName>MaBase</Mirror_ServerName>
<Mirror_ServerIPAddress>123.456.78.90</Mirror_ServerIPAddress>
<Mirror_TimeInterval>06:00:00</Mirror_TimeInterval>
<Mirror_LastBackupNumber>[0000]</Mirror_LastBackupNumber>
<Mirror_ErrorSMTPServer>smtp.4d.fr</Mirror_ErrorSMTPServer>
<Mirror_ErrorEMail>miroir@4d.fr</Mirror_ErrorEMail>
<Mirror_ErrorEMailAuthentication>Non</Mirror_ErrorEMailAuthentication>
<Mirror_ErrorEMailUsername>Aucun</Mirror_ErrorEMailUsername>
<Mirror_ErrorEMailPassword>Aucun</Mirror_ErrorEMailPassword>
</PreferenceSetting>
</Mirror>

Plus d'informations sur la sauvegarde et la modification du fichier XML dans un chapitre suivant.

Côté client

Cela fait plusieurs années qu'il n'y a pas eu de note technique traitant du paramétrage et du contrôle du serveur depuis une machine cliente. Si vous êtes familiarisé avec cette technique, allez directement au chapitre suivant.

Le fichier de préférences XML est stocké sur la machine du 4D Serveur, dans son dossier Préférences. Ce serait compliqué de manipuler ce fichier directement depuis une machine cliente, et de modifier son contenu. Il serait également mal venu de devoir aller sur la machine cliente et d'exécuter une méthode sur le serveur (Executer sur serveur) qui afficherait un dialogue sur la machine Serveur, et ensuite courir à cette machine pour saisir les données.

Nous allons plutôt utiliser une communication interprocess pour effectuer cette tâche, à l'aide des commandes :
LIRE VARIABLE PROCESS
et
ECRIRE VARIABLE PROCESS

Pour ces deux commandes, vous devez spécifier un numéro de process, le nom de la variable dans l'autre process, ainsi que la variable du process local qui reçoit ou contient la valeur courante. Si le numéro de process est négatif, cela signifie que vous vous adressez à un numéro de process sur le serveur, pas sur le client. Pour finir, nous pouvons faire un Executer sur serveur pour que le serveur mette son fichier à jour.

Tout ce dont nous avons besoin est écrit dans la méthode formulaire pour le dialogue qui permet de modifier les préférences.

Si (Faux)
` Methode formulaire: Miroir_Preferences dans la table [zDialogues]
` Note technique : mirroring avec 4D 2004
` Créée par: Kent Wilbur
` But: gère l'interface utilisateur pour le paramétrage des préférences du miroir

¾f_Version2004x2:=Vrai
¾fK_Wilbur:=Vrai

Fin de si

` Declarer les variables locales
C_ENTIER LONG($LApplicationType)
C_ENTIER LONG($LFormEvent)
C_ENTIER LONG($LProcessID)
C_TEXTE($tTimeIncrement)
C_TEXTE($tSettingsFullPath)

` Declarer les paramètres par default
$tSettingsFullPath:=Miroir_tCheminMiroir

$LFormEvent:=Evenement formulaire
$LApplicationType:=Type application

Au cas ou
: ($LFormEvent=Sur chargement )
TABLEAU TEXTE(atMirror_Authentication;2)
atMirror_Authentication{1}:="Non"
atMirror_Authentication{2}:="Oui"

Au cas ou
: ($LApplicationType=4D Client )
LIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_ServerType;¾tMirror_ServerType)
LIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_DatabaseName;¾tMirror_DatabaseName)
LIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_ServerIPAddress;¾tMirror_ServerIPAddress)
LIRE VARIABLE PROCESS(LMirrorProcessID;¾hMirror_TimeInterval;vhMirror_TimeInterval)
LIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_LastLogNumber;¾tMirror_LastLogNumber)
LIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_SMTPServer;¾tMirror_SMTPServer)
LIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_ErrorEMailAccount; ¾tMirror_ErrorEMailAccount)
LIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_AuthenticationRequired; ¾tMirror_AuthenticationRequired)
LIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_AuthenticationUserName;¾tMirror_AuthenticationUserName)
LIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_AuthenticationPassword; ¾tMirror_AuthenticationPassword)

: ($LApplicationType=4e Dimension )
Miroir_GestPrefsMiroir ("Load")  `charger les préférences miroir

Sinon
NE PAS VALIDER
Fin de cas

atMirror_Authentication:=Chercher dans tableau(atMirror_Authentication; ¾tMirror_AuthenticationRequired)
Si (atMirror_Authentication=2)  ` authentification requise
CHOIX SAISISSABLE(¾tMirror_AuthenticationUserName;Vrai)
CHOIX SAISISSABLE(¾tMirror_AuthenticationPassword;Vrai)
Sinon
CHOIX SAISISSABLE(¾tMirror_AuthenticationUserName;Faux)
CHOIX SAISISSABLE(¾tMirror_AuthenticationPassword;Faux)
Fin de si

$tTimeIncrement:=Sous chaine(Chaine(¾hMirror_TimeInterval;h mn );1;2)
atMirror_Hr:=Chercher dans tableau(atMirror_Hr;$tTimeIncrement)
$tTimeIncrement:=Sous chaine(Chaine(¾hMirror_TimeInterval;h mn );4;2)
atMirror_Min:=Chercher dans tableau(atMirror_Min;$tTimeIncrement)

`définir les valeurs par défaut du dialogue
Mirror_ckLaunchProcess:=0
Au cas ou
: (¾tMirror_ServerType="Mirror")
CHOIX VISIBLE(Mirror_ckLaunchProcess;Faux)
CHOIX VISIBLE(*;"MirrorStatus";Vrai)
tMessage:="Ce serveur tourne en tant que serveur miroir"

: (Type application#4D Client )
CHOIX VISIBLE(Mirror_ckLaunchProcess;Faux)
CHOIX VISIBLE(*;"MirrorStatus";Vrai)
tMessage:="Le miroir ne peut pas s'effectuer sur un 4D Mono"

: (LMirrorProcessID=-1)  ` Il n'y a pas de process miroir tournant sur le serveur
CHOIX VISIBLE(Mirror_ckLaunchProcess;Vrai)
CHOIX VISIBLE(*;"MirrorStatus";Faux)
tMessage:=""
Sinon
CHOIX VISIBLE(Mirror_ckLaunchProcess;Faux)
CHOIX VISIBLE(*;"MirrorStatus";Vrai)
tMessage:="Le miroir tourne sur ce serveur"
Fin de cas

: ($LFormEvent=Sur libération )
TABLEAU TEXTE(atMirror_Authentication;0)

Si (bOK=1)
Au cas ou
: ($LApplicationType=4D Client )
ECRIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_DatabaseName; ¾tMirror_DatabaseName)
ECRIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_ServerIPAddress; ¾tMirror_ServerIPAddress)
ECRIRE VARIABLE PROCESS(LMirrorProcessID;¾hMirror_TimeInterval;¾hMirror_TimeInterval)
ECRIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_SMTPServer;¾tMirror_SMTPServer)
ECRIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_ErrorEMailAccount; ¾tMirror_ErrorEMailAccount)
ECRIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_AuthenticationRequired; ¾tMirror_AuthenticationRequired)
ECRIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_AuthenticationUserName; ¾tMirror_AuthenticationUserName)
ECRIRE VARIABLE PROCESS(LMirrorProcessID;¾tMirror_AuthenticationPassword; ¾tMirror_AuthenticationPassword)
Si (Mirror_ckLaunchProcess=1)
$LProcessID:=Executer sur serveur("Miroir_GestPrefsMiroir";16000;"$UpdateMirrorPreferences"; "Save&LaunchFromClient")  `sauvegarder et lancer à partir du client
Sinon
$LProcessID:=Executer sur serveur("Miroir_GestPrefsMiroir";16000;"$UpdateMirrorPreferences"; "SaveFromClient")
`sauvegarder à partir du client
Fin de si

: ($LApplicationType=4e Dimension )
Miroir_GestPrefsMiroir ("Save")  `sauvegarder
Fin de cas

Fin de si

Fin de cas

`Fin de méthode formulaire

Le process du mirroring

Côté serveur principal

La responsabilité d'accomplissement du processus de mirroring incombe en fait au serveur principal. En effet, c'est un process en sommeil jusqu'à ce qu'il soit réveillé par la planification programmée pour créer un nouveau fichier d'historique et créer un miroir de la base. Les presque 100 % de la méthode P_MiroirProcess ne servent pratiquement qu'à simplement déterminer à quel moment le processus doit être lancé. Elle gère la modification de la planification programmée en fonction des modifications des préférences pendant la mise en veille du process. Quand le client met ses paramètres à jour, le process se réveille et recalcule à quel moment le prochain processus de mirroring doit être déclenché. Ce moment est déterminé par un mécanisme basé sur un format de date/heure (timestamp).

Si (Faux)
` Methode: P_MiroirProcess
` Note technique : mirroring avec 4D 2004
` Créée par: Kent Wilbur

` But: démarre le processus du mirroring

<>f_Version2004x2:=Vrai
<>fK_Wilbur:=Vrai

Fin de si

` Declarer les varaibles locales
C_ALPHA(6;$sVersion)
C_ENTIER LONG($LDelayTicks)
C_ENTIER LONG($LErrorCode)

C_TEXTE($tClosedLogFileName)
C_TEXTE($tDateTimeDelay)
C_TEXTE($tLogFilePath)
C_HEURE($hCurrentTime)
C_HEURE($hDelayInterval)
C_HEURE($hDelayUntil)
C_HEURE($hTimeChange)

$sVersion:=Version application
Miroir_GestPrefsMiroir ("Load")  `charger

Au cas ou
: (Type application#4D Server )
ALERTE("Le Mirroring ne fonctionne qu'avec 4D Server.")
: (Num($sVersion)<802)
ALERTE("Le Mirroring requiert la Version 2004.2 ou ultérieure.")
Sinon
Si (<>hMirror_TimeInterval#?00:00:00?)  ` Si un délai a été défini
$hDelayInterval:=<>hMirror_TimeInterval
$hCurrentTime:=Heure courante  ` défini maintenant pour un usage ultérieur
Repeter
` Cette section est nécessaire parce que d'autres process peuvent appeler ce process
`pour poursuivre.
`, Mais nous ne voudrions pas que le process s'active trop tôt
Si ($hDelayInterval<?00:15:00?)
$hCurrentTime:=Heure courante
` Moins de 15 minutes, défini pour se référer à l'heure du dernier mirorring
` une fois fini plutôt qu'à son heure de début
Fin de si

$hDelayUntil:=$hCurrentTime+$hDelayInterval
Si ($hDelayUntil>?24:00:00?)
$tDateTimeDelay:=GEN_tDateTimeStamp (Ajouter a date(Date du jour;0;0;1);$hCurrentTime+
$hDelayInterval-?24:00:00?)
Sinon
$tDateTimeDelay:=GEN_tDateTimeStamp (Date du jour;$hDelayUntil)
Fin de si

Repeter
$hDelayUntil:=Heure(Sous chaine($tDateTimeDelay;9;2)+":"
+Sous chaine($tDateTimeDelay;11;2)+":"+Sous chaine($tDateTimeDelay;13;2))

Si (Num(Sous chaine($tDateTimeDelay;3;2))>(Jour de(Date du jour)))
` Prochaine planification après minuit
$LDelayTicks:=($hDelayUntil+?24:00:00?)-$hCurrentTime*60
Sinon
$LDelayTicks:=$hDelayUntil-$hCurrentTime*60
Fin de si
ENDORMIR PROCESS(Numero du process courant;$LDelayTicks)
`TRACE
` Il est possible qu'une machine Cliente puisse être en train de modifier l'intervalle de temps
` Si c'est vrai, l'heure doit être recalculée
Au cas ou
: ($hDelayInterval=<>hMirror_TimeInterval)  ` Pas de changements nécessaires,
` maintenir simplement le délai

: ($hDelayInterval><>hMirror_TimeInterval)  ` l'intervalle du Mirroring est raccourci
$hTimeChange:=($hDelayInterval-<>hMirror_TimeInterval)
$hDelayUntil:=Heure(Sous chaine($tDateTimeDelay;9;2)+":"
+Sous chaine($tDateTimeDelay;11;2)+":"+Sous chaine($tDateTimeDelay;13;2))
Si (Num(Sous chaine($tDateTimeDelay;3;2))>(Jour de(Date du jour)))
` L'heure prévue est pour demain
$hDelayUntil:=$hDelayUntil+?24:00:00?
Fin de si

Au cas ou
: (($hCurrentTime+$hTimeChange)<$hDelayUntil)  ` Le nouvel horaire est avancé
`par rapport à l'horaire planifié, simplement faire un reset sur le temps
$hDelayUntil:=$hDelayUntil-$hTimeChange
Si ($hDelayUntil>?24:00:00?)
$tDateTimeDelay:=GEN_tDateTimeStamp (Ajouter a date(Date du jour;0;0;1); $hDelayUntil-?24:00:00?)
Sinon
$tDateTimeDelay:=GEN_tDateTimeStamp (Date du jour;$hDelayUntil)
Fin de si
: (($hCurrentTime+$hTimeChange)>$hDelayUntil)
` L'ancien horaire précède le nouveau. Planifie avec l'ancien.
Fin de cas
$hDelayInterval:=<>hMirror_TimeInterval  ` Reset pour la valeur courante

Sinon   ` L'intervalle de Mirroring interval a été augmenté
$hDelayUntil:=Heure(Sous chaine($tDateTimeDelay;9;2)+":"
+Sous chaine($tDateTimeDelay;11;2)+":"+Sous chaine($tDateTimeDelay;13;2))
Si (Num(Sous chaine($tDateTimeDelay;3;2))>(Jour de(Date du jour)))
` L'heure prévue est pour demain
$hDelayUntil:=$hDelayUntil+?24:00:00?
Fin de si
$hDelayUntil:=$hDelayUntil+(<>hMirror_TimeInterval-$hDelayInterval)
Si ($hDelayUntil>?24:00:00?)
$tDateTimeDelay:=GEN_tDateTimeStamp (Ajouter a date(Date du jour;0;0;1); $hDelayUntil-?24:00:00?)
Sinon
$tDateTimeDelay:=GEN_tDateTimeStamp (Date du jour;$hDelayUntil)
Fin de si

$hDelayInterval:=<>hMirror_TimeInterval  ` Reset pour la valeur courante
Fin de cas

Jusque ((GEN_tDateTimeStamp (Date du jour;Heure courante)>=$tDateTimeDelay) | (<>hMirror_TimeInterval=?00:00:00?))

Si (<>hMirror_TimeInterval#?00:00:00?)  ` Nous n'arrêtons pas le mirroring
$hCurrentTime:=Heure courante  ` Reset pour le prochain interval de temps
$LErrorCode:=Miroir_EnvFichierHist
Au cas ou
: ($LErrorCode=1)  ` Mirroring réussi !
Sinon
` Il est possible que vous souhaitiez figer le mirroring si une erreur 1403, 1412 ou 1410 survient
` Une erreur 1409 survient quand des transactions empêchent la création d'un nouveau fichier d'historique
` Une erreur 1411 survient quand une opération critique empêche la création d'un nouveau fichier d'historique
`, Mais je vous laisse décider vous-mêmes
` Si vous avez l'intention de stopper le mirroring, vous aurez besoin de modifier la
`ligne <>fQuitter pour arrêter la boucle
Fin de cas
Fin de si

Jusque ((<>fQuitter) | (<>hMirror_TimeInterval=?00:00:00?))
Fin de si

Fin de cas
`Fin de méthode

Notez bien qu'il est possible que des erreurs fatales surviennent, empêchant les processus de mirroring suivants. Dans ce cas, je stoppe le processus de mirroring pour ne pas causer plus de dommages.

La méthode Miroir_EnvFichierHist fait le plus gros du travail. Premièrement, elle vérifie que le miroir est présent. Si le miroir n'est pas disponible, un nouveau fichier d'historique ne doit pas être créé.

Si (Faux)
` Methode: Miroir_EnvFichierHist
` Note technique : mirroring avec 4D 2004
` Créée par: Kent Wilbur
` but:

<>f_Version2004x2:=Vrai
<>fK_Wilbur:=Vrai

Fin de si

` Declarer les parametres
C_ENTIER LONG($0)
C_BOOLEEN($1)

` Declare les variables locales
C_ENTIER LONG($LAbortCounter)
C_ENTIER LONG($LError)
C_ENTIER LONG($LLastSublogNumber)
C_ENTIER LONG($LPosition)
C_ENTIER LONG($LSMTPid)
C_TEXTE($tLastLogFile)
C_TEXTE($tLogFilePath)
C_TEXTE($tIntegrateType)

Mirror_LError:=0
$LError:=0
$LAbortCounter:=0

Si (Non(<>fQuitter))
Au cas ou
: (proxy_SOAP_GererMiroir (<>tMirror_DatabaseName;"VerifyPresent"; <>tMirror_ServerIPAddress)#1)  ` la base de données n'est pas disponible
$LError:=-17050
SOAP_tErrorMessage:="La base miroir n'est pas disponible. Le Mirroring de "
+Chaine(Heure courante;h mn )+" du "+Chaine(Date du jour;Format court )+" n'a pas eu lieu."
Sinon
APPELER SUR ERREUR("ERROR_HandleMirrorError")
$tLastLogFile:=Nouveau fichier historique
` Retourne le chemin complet du fichier d'historique qui vient d'être fermé
APPELER SUR ERREUR("")
Fin de cas

Les erreurs sont capturées dans la méthode ERREUR_GestErreurMiroir qui assigne simplement une variable d'erreur (Error) à la variable Mirror_LError pour la gestion et la compréhension.

Au cas ou
: (Mirror_LError=1409) | (Mirror_LError=1411)  ` Une transaction ou une operation critique est en cours
$LAbortCounter:=$LAbortCounter+1
Si ($LAbortCounter>10)
$LError:=Mirror_LError
SOAP_tErrorMessage:="Une transaction ou une operation critique bloque le mirroring. Code d'erreur : "+Chaine(Mirror_LError)
Fin de si

Si la vérification de l'existence (VerifyPresence) du miroir passe sans problème, alors un nouveau fichier d'historique est créé. Si la création du nouveau fichier d'historique réussit, le fichier d'historique qui vient juste d'être fermé est stocké dans un BLOB et envoyé via SOAP au serveur miroir.

Note :
Comme je l'ai mentionné précédemment, j'utilise SOAP, mais d'autres mécanismes peuvent être développés.

N'hésitez pas à étudier le code de la méthode proxy_SOAP_GererMiroir, bien qu'elle ait été générée automatiquement par l'assistant Web Services et qu'elle n'ait pas été modifiée de manière significative, sauf pour utiliser les paramétrages des préférences pour la localisation du serveur miroir.

: (Longueur($tLastLogFile)>0)
$tIntegrateType:="IntegrateLog"
$LAbortCounter:=0

DOCUMENT VERS BLOB($tLastLogFile;Mirror_oLogFile)
Si (Taille BLOB(Mirror_oLogFile)>10000)  ` Si > sur 10 000 Ko, compresser le
`blob pour réduire l'utilisation de la bande passante
COMPRESSER BLOB(Mirror_oLogFile)
Fin de si

$tLogFilePath:=GEN_tCheminDossier ($tLastLogFile)
Si ($tLogFilePath#$tLastLogFile)  `extraire le chemin complet
$tLastLogFile:=(Sous chaine($tLastLogFile;Longueur($tLogFilePath)+1))
Fin de si
$LError:=proxy_SOAP_GererMiroir (<>tMirror_DatabaseName;$tIntegrateType;
<>tMirror_ServerIPAddress;->Mirror_oLogFile;$tLastLogFile)
` Envoyer le fichier d'historique au serveur
Si ($LError=1)
<>tMirror_LastLogNumber:=$tLastLogFile
Miroir_GestPrefsMiroir ("Save")  `sauvegarder
Fin de si
FIXER TAILLE BLOB(Mirror_oLogFile;0)  ` libérer la mémoire
Fin de cas

Fin de si

$0:=$LError

Pour finir, s'il y a des erreurs, des messages d'alertes seront envoyés par courrier électronique à l'adresse renseignée.

Au cas ou
: ($LError=1)  ` Mirroring OK, rien de plus à faire
: ($LError=0)  ` Mirroring ne s'est pas déclenché, mais il n'y a rien à faire
: (Longueur(<>tMirror_SMTPServer)=0) & (<>tMirror_SMTPServer#"Aucun")
` Rien à faire, pas d'erreur à envoyer
: (Position(".";<>tMirror_SMTPServer)<1) |
((Position(".";<>tMirror_SMTPServer))=(Longueur(<>tMirror_SMTPServer)))
` format invalide pour le serveur de mail (il faut un point pour être valide et pas un point à la fin)
Sinon
` Message d'erreur pour l'email
` Note : ceci peut être ajouté à un système de messagerie existant dans une base de données
` Si un tel système implique la sauvegarde d'enregistrements
` Assurez-vous que ceci ne s'effectue PAS sur le serveur miroir, seulement sur le serveur principal
` le problème avec cette solution est que si le serveur SMTP est HS, le message ne sera pas envoyé
` donc avoir un système de messagerie déjà présent dans la base peut-être une meilleure solution
$LError:=SMTP_New ($LSMTPid)
$LError:=SMTP_Host ($LSMTPid;<>tMirror_SMTPServer)
$LError:=SMTP_From ($LSMTPid;<>tMirror_ErrorEMailAccount)
$LError:=SMTP_Subject ($LSMTPid;"Error with Mirroring for "+<>tMirror_DatabaseName)
$LError:=SMTP_To ($LSMTPid;<>tMirror_ErrorEMailAccount)
$LError:=SMTP_Body ($LSMTPid;SOAP_tErrorMessage)
Si (<>tMirror_AuthenticationRequired="Yes")
$LError:=SMTP_Auth ($LSMTPid;<>tMirror_AuthenticationUserName; <>tMirror_AuthenticationPassword)
Fin de si
$LError:=SMTP_Send ($LSMTPid)
$LError:=SMTP_Clear ($LSMTPid)

Fin de cas
SOAP_tErrorMessage:=""
`Fin de méthode

Côté serveur miroir

Le serveur miroir ne fait basiquement rien, si ce n'est « s'asseoir et attendre ». Il ne fait même tourner aucun process. Si votre base de données utilise des procédures stockées qui tournent sur le serveur, il est préférable que vous les désactiviez et vérifiez que le serveur est un serveur miroir avant de lancer ces procédures stockées.

Quand un appel SOAP du serveur principal atteint la méthode d'entrée SOAP_GererEventsMiroir, le serveur miroir se met en marche. Il retourne 1 si l'action a réussi ou un code d'erreur si l'action a échoué.

Il possède deux actions. La première, vérifier que la base de données miroir existe avec le bon nom. La seconde, intégrer le fichier d'historique.

Si (Faux)
` Methode: SOAP_GererEventsMiroir
` Note technique : mirroring avec 4D 2004
` Créée par: Kent Wilbur

` But: Gérer les requêtes SOAP pour le process du mirroring

<>f_Version2004x2:=Vrai
<>fK_Wilbur:=Vrai

Fin de si

` Declarer les variables pour les types SOAP
C_BLOB(SOAP_oMirrorBLOB)
C_ENTIER LONG(SOAP_LMirrorResult)
C_TEXTE(SOAP_tMirrorAction)
C_TEXTE(SOAP_tMirrorDatabaseName)
C_TEXTE(SOAP_tMirrorLogFile)

` Declarer les variables locales
C_ENTIER LONG($LCompressed)
C_ENTIER LONG($LErrorCode)
C_TEXTE($tDatabasePath)
C_TEXTE($tDatabaseName)

DECLARATION SOAP(SOAP_tMirrorDatabaseName;Est un texte ;SOAP entrée ; "SOAP_tMirrorDatabaseName")
DECLARATION SOAP(SOAP_tMirrorAction;Est un texte ;SOAP entrée ;"SOAP_tMirrorAction")
DECLARATION SOAP(SOAP_oMirrorBLOB;Est un BLOB ;SOAP entrée ;"SOAP_oMirrorBLOB")
DECLARATION SOAP(SOAP_tMirrorLogFile;Est un texte ;SOAP entrée ;"SOAP_tMirrorLogFile")
DECLARATION SOAP(SOAP_LMirrorResult;Est un entier long ;SOAP sortie ;"SOAP_LMirrorResult")

LECTURE SEULEMENT(*)  ` définir les tables en lecture seules

$LErrorCode:=0
Mirror_LError:=0

$tDatabaseName:=Fichier structure
$tDatabasePath:=GEN_tCheminDossier ($tDatabaseName)
$tDatabaseName:=(Sous chaine($tDatabaseName;Longueur($tDatabasePath)+1))
Si (SOAP_tMirrorDatabaseName=$tDatabaseName)
Au cas ou
: (SOAP_tMirrorAction="VerifyPresent")  `vérifier la présence de la base
SOAP_LMirrorResult:=1  ` la base de données est ici

: (SOAP_tMirrorAction="IntegrateLog@")  `intégrer le log
LECTURE ECRITURE(*)  ` définir les tables en lecture écriture pour l'intégration du miroir
LIRE PROPRIETES BLOB(SOAP_oMirrorBLOB;$LCompressed)
Si ($LCompressed#Non compressé )
DECOMPRESSER BLOB(SOAP_oMirrorBLOB)
Fin de si
APPELER SUR ERREUR("ERREUR_GestErreurMiroir")
BLOB VERS DOCUMENT($tDatabasePath+SOAP_tMirrorLogFile;SOAP_oMirrorBLOB)
INTEGRER FICHIER HISTORIQUE($tDatabasePath+SOAP_tMirrorLogFile)
APPELER SUR ERREUR("")
Si (OK=1)
SOAP_LMirrorResult:=OK
Sinon
$LErrorCode:=Mirror_LError
Fin de si

Si (SOAP_tMirrorAction="IntegrateLog&Backup")  `intégrer le log + backup
SAUVEGARDER
Fin de si

Fin de cas

Sinon
$LErrorCode:=-17051
Fin de si

Si ($LErrorCode#0)
SOAP_LMirrorResult:=$LErrorCode
SOAP_GererErreur ($LErrorCode)
Fin de si
`Fin de méthode

Si une erreur survient pendant l'intégration ou autre, l'erreur est traduite en numéro et message d'erreur, et une erreur SOAP est retournée à la base de données principale.

Si (Faux)
` Method: SOAP_GererErreur
` Note technique : mirroring avec 4D 2004
` Créée par: Kent Wilbur
` but: contient les descriptions des erreurs

<>f_Version2004x2:=Vrai
<>fK_Wilbur:=Vrai

Fin de si

`Declarer les parametres
C_ENTIER LONG($1;$LErrorCode)

` Reassigner pour la lisibilité
$LErrorCode:=$1

Au cas ou
: ($LErrorCode=-1403)
SOAP_tErrorMessage:="Pas de fichier d'historique. La base miroir n'est pas configurée pour avoir un fichier d'historique. "+"L'integration ne peut pas se faire."
: ($LErrorCode=-1410)
SOAP_tErrorMessage:="Les fichiers d'historique sont non synchronisés. L'intégration ne peut pas se fai"+"re."+" Vous devez reconstruire le serveur miroir de zéro."
: ($LErrorCode=-1412)
SOAP_tErrorMessage:="La base miroir n'est pas lancée sous 4D Server. L'intégration ne peut se faire qu'avec 4D Serveur."
: ($LErrorCode=-17051)
SOAP_tErrorMessage:="Nom de base de données invalide. Impossible de fusionner les données dans la base avec ce nom. "+"Vérifiez les préférences du miroir."
Fin de cas

ENVOYER ERREUR SOAP(SOAP erreur client ;Chaine($LErrorCode)+" - "+SOAP_tErrorMessage)
`Fin de méthode

Maintenir les utilisateurs hors de la base miroir

Le serveur principal et le serveur miroir sont tous les deux sur le réseau. Il est donc probable qu'un utilisateur se connecte accidentellement au serveur miroir. Voici quelques petites choses que vous pouvez faire pour éviter que cela arrive.

D'abord, au moins, renommez le nom de publication sur le réseau de votre base de données miroir. Faites-le dans les préférences de la base, dans le panneau Client/Serveur.

Si la base de données est nommée différemment, il y a peu de chance qu'un utilisateur s'y connecte accidentellement.

Une solution plus sécurisante encore est de changer le numéro de port de publication pour le 4D Client. Ainsi l'application 4D Client normal ne peut pas accidentellement se connecter à la mauvaise base de données.

Note : n'oubliez pas de restaurer le numéro de port si vous avez besoin d'installer le miroir comme base principale.

Restauration d'urgence

Catastrophe mineure

Le système de mirroring est prévu pour restaurer rapidement une base de données suite à des catastrophes mineures. S'il y a beaucoup de petites catastrophes, j'appelle cela une catastrophe d'ordre majeur. Par petite catastrophe, je veux dire que la base de données sur le serveur principal est inopérante. Toutes les données sont perdues. La restauration est possible, mais prendra des heures pour récupérer les données des sauvegardes.

Si vous avez accès au disque dur du serveur principal pour retrouver le dernier fichier d'historique ouvert qui n'a pas encore été intégré à la base, la restauration est facile. Le dernier fichier d'historique a besoin d'être intégré dans la base miroir. Ci-dessous vous trouverez les démarches nécessaires pour la base exemple. Pour vos propres bases, vous devrez utiliser au moins quelques-uns de ces points :

1. Déplacer simplement le fichier d'historique sur un 4D Client, dans un dossier spécifique. C'est nécessaire, car le dernier fichier d'historique aura besoin d'être fusionné avec la machine miroir avant qu'il puisse être installé sur le serveur principal. La commande INTEGRER FICHIER HISTORIQUE ne peut être appliquée que sur le serveur. C'est pourquoi la machine du 4D Client va temporairement prendre la place du serveur principal crashé et envoyer le dernier fichier au serveur miroir pour l'intégrer ;

2. Lancer le 4D Client et se connecter à la base miroir. Selon les mesures de sécurité choisies plus haut, il est possible qu'il soit nécessaire de changer le numéro de port soit du 4D Client soit du 4D Server ;

3. Aller en mode utilisation et exécuter la méthode E_RestDataLogsMiroir. Nous étudierons ce code dans le prochain chapitre ;

4. Sélectionner le fichier d'historique à fusionner ;

5. Aller sur le serveur et modifier le numéro de port si besoin ;

6. Quitter le serveur miroir ;

7. Supprimer les préférences du miroir ;

8. Lancer le serveur et sélectionner le serveur qui sera le serveur principal. Vous voilà à nouveau opérationnel.

Si vous ne pouvez pas récupérer le dernier fichier d'historique ouvert vous devrez le faire sans ces données. Dans ce cas, passez les points 1 à 4 ci-dessus et allez directement au point 5.

Catastrophe majeure

Le pire scénario possible est que les données du serveur principal et du serveur miroir soient endommagées. Dans ce cas, vous devez reprendre une sauvegarde et repartir de là. Cependant, la sauvegarde originale ne contient aucun des segments du fichier d'historique. Il n'est pas important de savoir quelle base de données est utilisée. Suivez cette procédure ci-dessous pour récupérer une base suite à une catastrophe majeure.

1. Restaurer la base de données à partir de la dernière sauvegarde ;

2. Déplacer tous les fichiers d'historiques sur le 4D Client dans un dossier à part ;

3. Lancer le 4D Client and connectez-vous au serveur ;

4. Aller en mode Utilisation et exécuter la méthode E_RestDataLogsMiroir ;

5. Sélectionnez un des fichiers d'historiques à être fusionné ;

6. Après l'avoir intégré, faites une sauvegarde et lancez le tout à nouveau en installant le miroir comme base principale. Vous êtes à nouveau opérationnel.

Note : il est possible qu'il soit nécessaire de fusionner séparément le fichier d'historique final comme vous l'avez fait dans les points plus haut lors de la restauration après une catastrophe mineure : le code ne distingue pas les segments de log qui sont actifs ou fermés.

Si (Faux)
` Method: E_RestDataLogsMiroir
` Note technique : mirroring avec 4D 2004
` Créée par: Kent Wilbur

` But:  Intègre les fichiers d'historique dans la base de données restaurées depuis une sauvegarde
` pas le miroir

<>f_Version2004x2:=Vrai
<>fK_Wilbur:=Vrai

Fin de si

` Declarer les variables locales
TABLEAU TEXTE($atDocuments;0)
TABLEAU TEXTE($atLogFiles;0)
C_BLOB($oBlob)
C_ENTIER LONG($i)
C_ENTIER LONG($LApplicationType)
C_ENTIER LONG($LError)
C_ENTIER LONG($LPid)
C_ENTIER LONG($LPosition)
C_TEXTE($tFileName)
C_TEXTE($tFolderPath)
C_HEURE($hDocRef)

$LApplicationType:=Type application

Au cas ou
: ($LApplicationType=4D Client )
ALERTE("Veuillez localiser le fichier d'historique à intégrer.")

$hDocRef:=Ouvrir document("";"";Lire chemin accès )
` montre tous les fichiers, mais si un historique n'est pas sélectionné, ça ne marchera pas

Si (OK=1)
FERMER DOCUMENT($hDocRef)
$tFolderPath:=GEN_tCheminDossier (Document)
$tFileName:=Sous chaine(Document;Longueur($tFolderPath)+1)
` retourne le nom du fichier d'historique sélectionné

` retourne les infos nécessaires du serveur
SOAP_tMirrorDatabaseName:=""
tMirror_ThisServerIPAddress:=""

Bien que nous travaillons sur une machine 4D Client, il est plus facile d'intégrer le fichier d'historique en utilisant les mêmes méthodes SOAP utilisées par le serveur principal. Le code d'intégration (SOAP) a besoin du nom de la base et de l'adresse IP du serveur miroir. Ces données NE SONT PAS dans les préférences du miroir. Donc, nous allons utiliser une communication par interprocess entre le Client et le Serveur. Le code suivant lit les valeurs par la commande LIRE VARIABLE PROCESS.

$LPid:=Executer sur serveur("E_RestoreDataFromMirrorLogs";32000)
Repeter
PROCESS_MonDelai (Numero du process courant;2)  ` attend les informations du serveur
LIRE VARIABLE PROCESS($LPid;SOAP_tMirrorDatabaseName;SOAP_tMirrorDatabaseName)
LIRE VARIABLE PROCESS($LPid;tMirror_ThisServerIPAddress;tMirror_ThisServerIPAddress)
Jusque (Longueur(SOAP_tMirrorDatabaseName)>0)

Les segments du fichier d'historique ont un format spécifique [0000-0000]. Si nous fusionnons plusieurs segments, nous chercherons ce format et nous fusionnerons tous les fichiers qui correspondent à cette séquence dans leur nom de fichier et qui ont le numéro principal de sauvegarde adéquat.

$LPosition:=Position("[";$tFileName)
Si ($LPosition+10=Position("]";$tFileName))  ` s'assurer qu'on a bien les historiques
` du miroir à fusionner, et pas les historiques "normaux"
` nous devons retrouver tous les fichiers d'historiques correspondants
LISTE DES DOCUMENTS($tFolderPath;$atDocuments)

Boucle ($i;1;Taille tableau($atDocuments))

Au cas ou
: (Longueur($tFileName)#Longueur($atDocuments{$i}))
` pas la même longueur, pas un historique
: (Sous chaine($tFileName;1;$LPosition+5)#Sous chaine($atDocuments{$i};1;$LPosition+5))
` pas le même fichier de données ni numéro de sauvegarde
: (Sous chaine($tFileName;$LPosition+10)#Sous chaine($atDocuments{$i};$LPosition+10))
` pas un historique
Sinon
AJOUTER A TABLEAU($atLogFiles;$atDocuments{$i})
Fin de cas
Fin de boucle

Si (Taille tableau($atLogFiles)>0)  ` Intègre les fichiers d'historique
TRIER TABLEAU($atLogFiles)
Boucle ($i;1;Taille tableau($atLogFiles))

DOCUMENT VERS BLOB($tFolderPath+$atLogFiles{$i};Mirror_oLogFile)
Si (Taille BLOB(Mirror_oLogFile)<10000)  ` si plus grand que 10 000 octets,
`compresser le blob pour réduire l'utilisation de la bande passante
COMPRESSER BLOB(Mirror_oLogFile)
Fin de si
$LError:=proxy_SOAP_GererMiroir (SOAP_tMirrorDatabaseName;
"IntegrateLog";tMirror_ThisServerIPAddress;->Mirror_oLogFile;$atLogFiles{$i})
` envoie le fichier d'historique au serveur
Si ($LError#1)
ALERTE("Gros Problème!")
$i:=Taille tableau($atLogFiles)
Fin de si
FIXER TAILLE BLOB(Mirror_oLogFile;0)  ` libère la mémoire

Fin de boucle
Fin de si

Intégrer un seul fichier d'historique :

Sinon   ` Ajouter un fichier d'historique
DOCUMENT VERS BLOB($tFolderPath+$tFileName;Mirror_oLogFile)
Si (Taille BLOB(Mirror_oLogFile)<10000)  ` si plus grand que 10 000 octets, compresser le blob
`pour réduire l'utilisation de la bande passante
COMPRESSER BLOB(Mirror_oLogFile)
Fin de si
$LError:=proxy_SOAP_GererMiroir(SOAP_tMirrorDatabaseName;"IntegrateLog";
tMirror_ThisServerIPAddress;->Mirror_oLogFile;$tFileName)
` envoie le fichier d'historique au serveur

Si ($LError#1)
ALERTE("Le fichier d'historique a été fusionné !")
Fin de si
FIXER TAILLE BLOB(Mirror_oLogFile;0)  ` libère la mémoire

Fin de si
Fin de si

C'est ici que le 4D Serveur cherche ses informations et les sauvegarde dans des variables process. Une fois sauvegardé, le process se met en attente quelques secondes et meurt. Pendant ces quelques secondes, le code donné plus haut lit le contenu des variables process.

: ($LApplicationType=4D Server )
SOAP_tMirrorDatabaseName:=""
$LError:=IT_MyTCPAddr (tMirror_ThisServerIPAddress;$tFileName)
$tDatabaseName:=Fichier structure
$tDatabasePath:=GEN_tCheminDossier ($tDatabaseName)
SOAP_tMirrorDatabaseName:=(Sous chaine($tDatabaseName;Longueur($tDatabasePath)+1))
PROCESS_MonDelai (Numero du process courant;300)  ` laisse le process tourner assez longtemps pour récupérer la valeur de la variable

Sinon
ALERTE("Les fichiers d'historique ne peuvent être intégrés que depuis une machine cliente"+"avec 4D Client.")
Fin de cas
`Fin de méthode

La fonction Nouveau fichier historique et opérations critiques

La fonction Nouveau fichier historique et quelques opérations critiques comme les transactions et l'indexation sont mutuellement exclusives. Par exemple, vous ne pouvez pas créer un nouveau fichier d'historique pendant que le 4D Client ou une procédure stockée est en pleine transaction. Et vous ne pouvez pas débuter une nouvelle transaction pendant que Nouveau fichier historique est en cours.

Si votre base de données utilise les transactions, vous devrez planifier votre procédure de mirroring pour la lancer quand il n'y a plus d'utilisateurs actifs sur la base de données. Si des transactions sont en cours pendant la fonction Nouveau fichier historique, il est possible que le serveur gèle pendant quelques minutes, attendant la fin des transactions. Vous pouvez diminuer l'attente en modifiant quelques paramétrages par défaut dans les préférences de sauvegarde de la base de données.

Dans les paramètres généraux, assurez-vous que le bouton radio « Annuler la sauvegarde après une attente de n minutes » est sélectionné. Modifiez aussi le temps par défaut de 3 à 1 minute.

Ne mettez pas 0 minute. L'effet sera que la sauvegarde attend la fin des transactions, mais n'arrive jamais au timeout. Au besoin vous pouvez vérifier la bonne marche de la fonction Nouveau fichier historique en testant le code d'erreur éventuel. Si le code d'erreur est 1411, la fonction Nouveau fichier historique échoue à cause d'une opération critique. Habituellement parce qu'une transaction était en cours. Votre planification pourrait réessayer jusqu'à ce que la fonction réussisse, ou attendre le prochain lancement de l'opération. Ceci n'a pas été programmé dans la base exemple.

Conclusion

Créer un système de mirroring n'est plus une tâche purement administrative. La coopération du développeur est requise pour son implémentation. Ce n'est cependant pas une tâche difficile à accomplir. Les administrateurs de base de données 4D avaient souvent des problèmes pour installer un miroir, le développeur était donc de toute façon impliqué.

Les nouvelles commandes du mirroring rendent la chose plus flexible que dans les versions précédentes de 4D. Une interface conviviale peut être créée aisément pour administrer le mirroring.

Même si le mirroring était possible avec 4D 2004.2, je recommande d'utiliser au minimum 4D 2004.3 qui fixe un bug mineur (même si la base exemple ne l'aurait même pas rencontré).

La plupart des bases de données 4D n'ont pas besoin de base miroir. Mais pour celles dont c'est le cas, je suggère que vous vous penchiez sur ce qu'il est possible de faire avec ces deux nouvelles commandes.

Nouveau fichier historique

INTEGRER FICHIER HISTORIQUE

Télécharger la base exemple

Télécharger la base exemple.