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ération sur un fichier ou répertoire, ou, sur leurs attributs.

.Net

Ce composant n'est pas spécifiques aux applications Windows Forms et peut être indifféremment employé dans vos projets ASP.Net ou console.

1. 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.

1.1. 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 à outil. 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 :

Propriétés de File System Watcher

1.2. 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és "New". Voici un exemple, attention cela va être très bref :

Tout d'abord ne pas oublier de faire l'Imports du System.IO :

 
Sélectionnez

Imports System.IO

Pour l'instanciation il n'y a rien d'affolant...

 
Sélectionnez

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'ils 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, quelque soit la méthode que vous avez utilisé (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 reprends tout cela :

Initialisation du FileSystemWatcher
Sélectionnez

    ' 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.

2. Récupération des évènements survenus

2.1. Cas général

Pour récupérer les évènements, il suffit simplement 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 :

Gestion d'un évènement
Sélectionnez

    ' Evé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.

2.2. 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 disfonctionnements. 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 solutionner 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 :

Désactivation du contrôle
Sélectionnez

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ée par le designer de Visual Studio 2005, en effet dans ce dernier cas, par défaut la propriété SynchronizingObject est positionnée à "True".

3. 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é :

3.1. Initialisation du FileSystemWatcher

Le code ci dessous permet d'initaliser 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.

Initialisation du FileSystemWatcher
Sélectionnez

   ' 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.

3.2. Scruter la création de fichiers

Voici le code permettant de surveiller toute création dans le répertoire observé :

Création d'un fichier
Sélectionnez

    ' Evé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

3.3. Scruter l'effacement de fichiers

Voici le code permettant de surveiller tout effacement dans le répertoire observé :

Effacement d'un fichier
Sélectionnez

    ' Evé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

3.4. Scruter un changement effectué sur un fichier

Voici le code permettant de surveiller toute modification sur un fichier dans le répertoire observé :

Changement sur un fichier
Sélectionnez

    ' Evé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

3.5. Scruter le renommage de fichiers

Voici le code permettant de surveiller tout renommage d'un fichier dans le répertoire obeservé :

Renommage d'un fichier
Sélectionnez

    ' Evé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

3.6. 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 4 EventHandlers qui aurait dirigé les quatre types d'évèments vers les bonnes fonctions de la manière suivante :

Ajout des gestionnaires d'évènements
Sélectionnez

        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.

4. 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 !

4.1. 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émoires, 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.

4.2. 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éflechissant à 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 succeptible 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.