Introduction▲
La classe FileSystemWatcher fait partie du namespace System.IO, elle possède des méthodes et des propriétés qui nous permettent de surveiller l'activité d'un répertoire, ou d'une arborescence de répertoires. Il est alors possible de scruter des évènements se produisant sur tout ou partie de ce sous-ensemble de répertoires. Par évènement, je veux, par exemple, parler de modification d'un fichier, d'accès en lecture à un répertoire, de création d'un fichier… ou encore tout un ensemble d'opérations sur un fichier ou répertoire, ou, sur leurs attributs.
Ce composant n'est pas spécifique aux applications Windows Forms et peut être indifféremment employé dans vos projets ASP.Net ou console.
I. Utiliser le composant FileSystemWatcher▲
Il existe deux manières pour utiliser un composant FileSystemWatcher, soit entièrement par le code, soit en utilisant le designer de Visual Studio.
I-A. Avec le designer de Visual Studio▲
Avec Visual Studio, l'initialisation d'un composant FileSystemWatcher se limite à un simple « glisser/déposer » depuis la boite à outils. Dès lors un composant est ajouté en dessous de votre Form courant dans le designer. Ses propriétés, comme tout autre « composant visuel » sont accessibles dans la zone de propriétés, faire F4 si jamais celle-ci n'est pas visible. Voici une capture d'écran de ces propriétés :
I-B. Par le code▲
Pour ceux n'utilisant pas Visual Studio, ou n'aimant pas trop user du glisser/déposer de composants, vous pouvez bien évidemment initialiser entièrement votre composant par le code. Comme n'importe quel autre objet, vous devez l'initialiser à l'aide du mot clé « New ». Voici un exemple, attention cela va être très bref :
Tout d'abord ne pas oublier de faire l'Import du System.IO :
Imports
System.IO
Pour l'instanciation il n'y a rien d'affolant…
Dim
WithEvents
myFsWatcher As
New
FileSystemWatcher
Une fois que vous avez initialisé ce composant, que ce soit par le designer ou par le code, il n'est cependant pas utilisable en l'état. Il faut au minimum renseigner les propriétés suivantes :
Path : il s'agit du chemin complet du répertoire à surveiller ;
IncludeSubdirectories : booléen qui détermine s'il faut surveiller seulement (False) le répertoire désigné par la propriété Path, ou en plus les sous-répertoires qu'il contient, (True) ;
Filter : cette propriété vous permet de surveiller seulement un type de fichier en fonction de son extension (*.doc par exemple), ou alors tous les fichiers avec la valeur *.*, il s'agit d'ailleurs de la valeur par défaut ;
NotifyFilter : cette propriété contient en fait l'ensemble des propriétés qui pourront être surveillées par le FileSystemWatcher, en voici une liste exhaustive :
- Attributes : The attributes of the file or folder,
- CreationTime : The time the file or folder was created,
- DirectoryName : The name of the directory,
- FileName : The name of the file,
- LastAccess : The date the file or folder was last opened,
- LastWrite : The date the file or folder last had anything written to it,
- Security : The security settings of the file or folder,
- Size : The size of the file or folder.
Si vous voulez lister plusieurs types de paramètres à surveiller, ce qui représente la majorité des cas, vous devez simplement ajouter l'opérateur logique Or entre chaque paramètre.
En fonction de la méthode que vous avez choisie pour placer un composant FileSystemWatcher dans votre application, vous devrez utiliser soit la fenêtre de propriété de Visual Studio, soit saisir ces propriétés à la main. Comme pour tout autre composant, quelle que soit la méthode que vous avez utilisée (par le designer de Visual Studio ou par le code) vous pouvez saisir toutes ces propriétés dans le code.
Maintenant que nous avons étudié comment initialiser correctement le FileSystemWatcher, nous allons voir un petit exemple pratique qui reprend tout cela :
' Initialisation du FileSystemWatcher
Private
Sub
initFsWatcher
(
)
' Répertoire à surveiller
myFsWatcher.Path
=
Me
.folderToWatch
' Surveille les sous-répertoires
myFsWatcher.IncludeSubdirectories
=
True
' Surveille tous les fichiers sans exception
myFsWatcher.Filter
=
"*.*"
' Surveille les types d'évènements suivants
myFsWatcher.NotifyFilter
=
(
NotifyFilters.Attributes
_
Or
NotifyFilters.CreationTime
_
Or
NotifyFilters.DirectoryName
_
Or
NotifyFilters.FileName
_
Or
NotifyFilters.LastAccess
_
Or
NotifyFilters.LastWrite
_
Or
NotifyFilters.Security
_
Or
NotifyFilters.Size
)
' Permet de faire s'exécuter tous les EventHandlers dans le thread principal
myFsWatcher.SynchronizingObject
=
Me
' Active la levée d'évènements par le FileSystemWatcher
myFsWatcher.EnableRaisingEvents
=
True
End
Sub
Votre composant émet une notification dès qu'un évènement répond aux critères précédemment définis. Nous allons donc maintenant voir comment récupérer ces évènements.
II. Récupération des évènements survenus▲
II-A. Cas général▲
Pour récupérer les évènements, il suffit d'utiliser des EventHandlers. Vous allez voir que c'est d'une très grande simplicité. Ces EventHandlers sont en fait des méthodes qui vont être exécutées lorsque survient un évènement du type dont elles sont chargées d'assurer la surveillance.
Un bon exemple, valant mieux qu'une mauvaise explication, voici concrètement à quoi cela ressemble :
' Événements : changement
Private
Sub
myFsWatcher_Changed
(
ByVal
sender As
Object
, ByVal
e As
FileSystemEventArgs) _
Handles
myFsWatcher.Changed
Me
.rtbEvents.AppendText
(
"Un changement vient de se produire sur un fichier : _
" & e.Name & ControlChars.CrLf)
End
Sub
Vous voyez que nos EventHandlers sont chargés de récupérer un type d'évènement précis et d'ensuite effectuer la tâche que l'on souhaite : cela peut être l'affichage d'une information à l'écran, l'ajout d'un enregistrement dans un journal, ou encore la compression ou l'encodage du fichier ayant généré cet évènement… Vous l'aurez compris les possibilités sont en fait infinies et seront dictées par les besoins de votre application.
Après avoir vu le cas général, où tout se déroule pour le mieux dans le meilleur des mondes, voici maintenant une exception à la règle.
II-B. Utilisation avec le Framework .Net 2.0 et Windows Form▲
Si vous utilisez Visual Studio 2005 ou du moins le Framework .Net 2.0 vous allez être confronté à un problème, de par la conception des contrôles Windows Form et du Framework .Net 2.0. si vous souhaitez répercuter un évènement (de type Created, Changed, Renamed ou Deleted) émis par le FileSystemWatcher. En effet, les EventHandlers associés à ces évènements s'exécutent dans un thread différent (issu du pool de thread) du thread principal de votre application. Voilà d'ailleurs comment le MSDN nous explique cela :
L'accès aux contrôles Windows Form n'est pas fondamentalement thread-safe. Si vous avez deux ou plusieurs threads qui manipulent l'état d'un contrôle, il est possible de forcer le contrôle dans un état incohérent. D'autres bogues en rapport avec les threads sont possibles aussi, y compris les conditions de concurrence critique et de blocages. Il est important de faire en sorte que l'accès à vos contrôles s'effectue de manière thread-safe.
En clair, lorsqu'un autre thread, que le thread principal, essaye d'accéder à des méthodes ou des propriétés d'un contrôle il peut en résulter des comportements aléatoires de celui-ci et un certain nombre de dysfonctionnements. Pour éviter cela, depuis la version 2.0 du Framework une vérification est effectuée et une exception levée, dans le cas où votre application se trouve dans ce cas de figure.
Seuls quatre « EventHandlers » ne sont pas exécutés dans le même thread que la Windows Forms ; ils concernent les Events suivants :
- Created ;
- Deleted ;
- Renamed ;
- Changed.
Il existe trois moyens principaux de résoudre cela.
- Désactiver la vérification effectuée lors de l'exécution
Les vérifications effectuées par le Framework peuvent être désactivées en ajoutant la ligne de code suivant à votre application :
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls
=
False
On se retrouve ainsi dans la situation du Framework .Net 1.1 qui n'effectue pas de contrôles à ce niveau-là. Cependant, pour des raisons de fiabilité et de robustesse de votre application, cela n'est pas recommandé, vous vous exposez à des bugs relativement complexes. La solution « idéale » en l'état est d'utiliser les délégués.
- Utiliser des délégués
Je ne vais pas entrer dans le détail des délégués, mais sachez uniquement que nous allons les utiliser pour faire un appel asynchrone d'une méthode qui va être chargée d'afficher les informations souhaitées issues de l'évènement (Changed, Created, Deleted ou Renamed).
- Utilisation de la propriété SynchronizingObject
Cette propriété permet de vous assurer que les gestionnaires des quatre évènements qui peuvent poser problème seront exécutés dans le même thread que celui de la Form utilisant le FileSystemWatcher. Ainsi plus besoin d'utiliser les délégués qui rajoutent de la complexité là où ce n'est pas vraiment indispensable.
Voici comment positionner cette propriété pour que la synchronisation des EventHandlers soit réalisée avec la Form courante.
Remarque : je vous rappelle que ces « problèmes » d'InvalidOperationException ne se présentent que lorsque vous utilisez le Framework 2.0 et que votre composant FileSystemWatcher n'a pas été créé par le designer de Visual Studio 2005, en effet dans ce dernier cas, par défaut la propriété SynchronizingObject est positionnée à « True ».
III. Exemples d'utilisation d'un FileSystemWatcher▲
Ci-dessous des morceaux de code développé avec Visual Studio 2005, permettant d'utiliser un FileSystemWatcher pour « observer » des évènements prédéfinis sur un répertoire donné.
III-A. Initialisation du FileSystemWatcher▲
Le code ci-dessous permet d'initialiser le FileSystemWatcher, en l'état il va surveiller l'activité sur le répertoire « c:\rep_test\ » pour tous les fichiers s'y trouvant ainsi que dans ses sous-répertoires.
' Déclaration du FileSystemWatcher
Private
WithEvents
myFsWatcher As
New
FileSystemWatcher
' Contient le chemin complet du dossier qui va être surveillé
Private
folderToWatch As
String
=
"c:\rep_test\"
' Initialisation du FileSystemWatcher
Private
Sub
initFsWatcher
(
)
' Répertoire à surveiller
myFsWatcher.Path
=
Me
.folderToWatch
' Surveille les sous-répertoires
myFsWatcher.IncludeSubdirectories
=
True
' Surveille tous les fichiers sans exception
myFsWatcher.Filter
=
"*.*"
' Surveille les types d'évènements suivants
myFsWatcher.NotifyFilter
=
(
NotifyFilters.Attributes
_
Or
NotifyFilters.CreationTime
_
Or
NotifyFilters.DirectoryName
_
Or
NotifyFilters.FileName
_
Or
NotifyFilters.LastAccess
_
Or
NotifyFilters.LastWrite
_
Or
NotifyFilters.Security
_
Or
NotifyFilters.Size
)
' Permet de faire s'exécuter tous les EventHandlers dans le thread principal
myFsWatcher.SynchronizingObject
=
Me
' Active la levée d'évènements par le FileSystemWatcher
myFsWatcher.EnableRaisingEvents
=
True
End
Sub
Maintenant que notre FileSystemWatcher est opérationnel, il faut récupérer et traiter les évènements qui sont émis. Pour cela nous allons utiliser le code suivant.
III-B. Scruter la création de fichiers▲
Voici le code permettant de surveiller toute création dans le répertoire observé :
' Évènement : création
Private
Sub
myFsWatcher_Created
(
ByVal
sender As
Object
, ByVal
e As
FileSystemEventArgs) Handles
myFsWatcher.Created
Me
.rtbEvents.AppendText
(
"Un fichier vient d'être créé : "
&
e.Name
&
ControlChars.CrLf
)
End
Sub
III-C. Scruter l'effacement de fichiers▲
Voici le code permettant de surveiller tout effacement dans le répertoire observé :
' Événement : effacement
Private
Sub
myFsWatcher_Deleted
(
ByVal
sender As
Object
, ByVal
e As
FileSystemEventArgs) Handles
myFsWatcher.Deleted
Me
.rtbEvents.AppendText
(
"Un fichier vient d'être effacé : "
&
e.Name
&
ControlChars.CrLf
)
End
Sub
III-D. Scruter un changement effectué sur un fichier▲
Voici le code permettant de surveiller toute modification sur un fichier dans le répertoire observé :
' Événement : changement
Private
Sub
myFsWatcher_Changed
(
ByVal
sender As
Object
, ByVal
e As
FileSystemEventArgs) Handles
myFsWatcher.Changed
Me
.rtbEvents.AppendText
(
"Un changement vient de se produire sur un fichier : "
&
e.Name
&
ControlChars.CrLf
)
End
Sub
III-E. Scruter le renommage de fichiers▲
Voici le code permettant de surveiller tout renommage d'un fichier dans le répertoire observé :
' Événement : renommage
Private
Sub
myFsWatcher_Renamed
(
ByVal
sender As
Object
, ByVal
e As
System.IO.RenamedEventArgs
) Handles
myFsWatcher.Renamed
Me
.rtbEvents.AppendText
(
"Un fichier vient d'être renommé : "
&
e.OldName
&
" en "
&
e.Name
&
ControlChars.CrLf
)
End
Sub
III-F. Une petite remarque sur la gestion d'évènements▲
Plutôt que d'ajouter Handles myFsWatcher.« event » une solution aurait été de rajouter dans la fonction d'initialisation du FileSystemWatcher quatre EventHandlers qui auraient dirigé les quatre types d'évènements vers les bonnes fonctions de la manière suivante :
AddHandler
myFsWatcher.Changed
, AddressOf
myFsWatcher_Changed
AddHandler
myFsWatcher.Created
, AddressOf
myFsWatcher_Created
AddHandler
myFsWatcher.Deleted
, AddressOf
myFsWatcher_Deleted
AddHandler
myFsWatcher.Renamed
, AddressOf
myFsWatcher_Renamed
Cela aurait donc imposé de supprimer le Handles myFsWatcher.« event » après la signature des quatre fonctions de gestion des évènements.
IV. Les limitations de ce composant▲
Bien que ce composant soit vraiment d'une très grande utilité dans de nombreuses situations, et permette d'économiser beaucoup de temps de développement, il possède quand même quelques limitations qui peuvent s'avérer gênantes… Mais rassurez-vous il existe des moyens simples pour les contourner !
IV-A. Les risques de Buffer OverFlow▲
Un risque d'erreur non négligeable existe… au cas où un trop grand nombre d'évènements surviendraient en même temps, et au cas où les ressources, notamment mémoire, de la machine seraient insuffisantes, il y a un risque non négligeable de buffer overflow ! Dans ce cas des évènements pourraient être perdus et non notifiés. Une parade existe, il faut augmenter la taille du buffer grâce à la propriété InternalBufferSize, pour ainsi prévenir des soucis de Buffer Overlfow.
IV-B. Problème de gestion de la charge▲
Une autre difficulté que l'on peut rencontrer avec le FileSystemWatcher est le fait que si le nombre d'évènements à gérer est trop grand, alors très rapidement va se poser un problème de gestion de la charge, cela peut être par exemple le cas sur des serveurs de fichiers, ainsi il faut être prudent dans l'utilisation des FSW. Il faut savoir cibler les fichiers et évènements nécessaires, faute de quoi la machine faisant tourner le programme va devoir assumer des charges élevées, qui vont avoir un impact fort sur les performances. Il faut donc savoir utiliser les FSW avec modération et en réfléchissant à la charge potentielle à supporter.
Cependant, il faut quand même garder à l'esprit que le FileSystemWatcher est très performant et consomme peu de ressources, il n'est donc pas susceptible de plier à la moindre charge un peu forte.
Ressources▲
Conclusion▲
Comme vous avez pu le constater tout au long de cet article, du moins je le souhaite, FileSystemWatcher est un composant puissant, mais surtout adaptable à de nombreuses situations. Grâce à ce composant, vos applications pourront aisément surveiller les opérations effectuées sur un système de fichiers. Il faut cependant garder à l'esprit que même si ce composant est puissant, et relativement robuste il faut l'employer de manière raisonnable pour ne pas surcharger sa machine. En espérant que cet article vous aura aidé à comprendre le FileSystemWatcher, je vous donne rendez-vous pour un prochain article.