Отслеживание изменений в файловой системе

При разработке программ, активно использующих информацию о файловой системе, часто возникает необходимость получать и отслеживать изменения, происходящие с файлами и каталогами. Наиболее ярким примером такого слежения являются всякого рода клиенты для облачных хранилищ типа Яндекс.Диск, Dropbox и т.д. Такие клиенты следят за изменениями в конкретной папке на диске и при малейшем изменении инициируют процесс синхронизации с облаком. Сегодня мы рассмотрим то, как организовать отслеживание изменений в файловой системе с использованием механизмов предоставляемых в .NET C#

Класс FileSystemWatcher

Класс FileSystemWatcher расположен в пространстве имен System.IO. Этот класс ожидает уведомления файловой системы об изменениях и инициирует события при изменениях каталога или файла в каталоге. Ниже представлены основные свойства, методы и события класса FileSystemWatcher

  • Свойство EnableRaisingEvents — получает или задает значение, определяющее, доступен ли данный компонент
  • Свойство Events — возвращает список обработчиков событий, которые прикреплены к этому объекту
  • Свойство Filter — получает или задает строку фильтра, используемую для определения файлов, контролируемых в каталоге
  • Свойство Filters — возвращает коллекцию всех фильтров, используемых для определения файлов, контролируемых в каталоге
  • Свойство IncludeSubdirectories — получает или задает значение, показывающее необходимость контроля вложенных каталогов по указанному пути.
  • Свойство InternalBufferSize — получает или задает размер (в байтах) внутреннего буфера.
  • Свойство NotifyFilter — получает или задает тип отслеживаемых изменений
  • Свойство Path — возвращает или задает путь отслеживаемого каталога
  • Метод WaitForChanged(WatcherChangeTypes) — синхронный метод, возвращающий структуру, содержащую сведения о произошедших изменениях, с заданным типом изменений, которые вы хотите контролировать
  • Метод WaitForChanged(WatcherChangeTypes, Int32) — синхронный метод, возвращающий структуру, содержащую сведения о произошедших изменениях, с заданным типом изменений, которые вы хотите контролировать, и временем ожидания (в мс) до блокировки по ожиданию
  • Событие Changed — происходит при изменении файла или каталога по заданному пути Path.
  • Событие Created — происходит при создании файла или каталога по заданному пути Path.
  • Событие Deleted — происходит при удалении файла или каталога по заданному пути Path.
  • Событие Error — происходит, когда экземпляру FileSystemWatcher не удается продолжить отслеживание изменений, например, при переполнении внутреннего буфера.
  • Событие Renamed — происходит при переименовании файла или каталога по заданному пути Path.

Для работы с классом FileSystemWatcher мы должны создать объект этого класса, назначить для объекта тип отслеживаемых изменений, каталог, в котором эти изменения будут отслеживаться и обработчики событий. Рассмотрим несколько примеров использования класса FileSystemWatcher

Как в C# отследить изменение имени файла?

Для отслеживания изменений только в имени файла нам достаточно назначить свойству NotifyFilter значение NotifyFilters.FileName , написать обработчик и назначить его событию Renamed

using System;
using System.IO;

namespace FileSystem
{
    class Program
    {
        static void Main(string[] args)
        {
            string path = @"c:\CSharp Output\";
            string fileName = "File.txt";
            FileSystemWatcher watcher = new FileSystemWatcher(path, fileName);
            watcher.NotifyFilter = NotifyFilters.FileName;
            watcher.Renamed += RenamedEventHandler;
            watcher.EnableRaisingEvents = true;

            Console.WriteLine("Press enter to exit.");
            Console.ReadLine();
        }

        private static void RenamedEventHandler(object sender, RenamedEventArgs e)
        {
            Console.WriteLine($"Старое имя файла: {e.OldName}");
            Console.WriteLine($"Новое имя файла: {e.Name}");
            ((FileSystemWatcher)sender).Filter = e.Name;
            Console.WriteLine($"Теперь отслеживаем файл: {e.Name}");
        }
    }
}

Приложение отслеживает изменение имени файла, расположенного по пути c:\CSharp Output\File.txt При этом, при очередном изменении имени файла у объекта класса FileSystemWatcher переопределяется свойство Filter  для того, чтобы изменения в имени файла продолжали отслеживаться. Результат работы программы может быть примерно следующим:

Press enter to exit.
Старое имя файла: File.txt
Новое имя файла: МойФайл.txt
Теперь отслеживаем файл: МойФайл.txt
Старое имя файла: МойФайл.txt
Новое имя файла: MyFile.txt
Теперь отслеживаем файл: MyFile.txt
Старое имя файла: MyFile.txt
Новое имя файла: MyFile.pdf
Теперь отслеживаем файл: MyFile.pdf
В приложении для сокращения объема исходного кода упущены некоторые необходимые проверки, например, проверка существования файла на диске. В реальном приложении такие проверки лучше делать

Как в C# отслеживать изменения всех файлов и каталогов в определенном каталоге и всех его подкаталогах?

Отслеживание всех изменений в файловой системе немногим сложнее, чем отслеживание конкретного файла, как было показано в предыдущем примере. Необходимо также назначить определенные фильтры и написать обработчики для событий компонента. Например, вот так:

using System;
using System.IO;

namespace FileSystem
{
 

    class Program
    {
        static void Main()
        {
            using var watcher = new FileSystemWatcher(@"c:\CSharp Output\");

            watcher.NotifyFilter = NotifyFilters.Attributes
                                 | NotifyFilters.CreationTime
                                 | NotifyFilters.DirectoryName
                                 | NotifyFilters.FileName
                                 | NotifyFilters.LastAccess
                                 | NotifyFilters.LastWrite
                                 | NotifyFilters.Security
                                 | NotifyFilters.Size;

            watcher.Changed += OnChanged;
            watcher.Created += OnCreated;
            watcher.Deleted += OnDeleted;
            watcher.Renamed += OnRenamed;
            watcher.Error += OnError;

            watcher.Filter = "*.*";
            watcher.IncludeSubdirectories = true;
            watcher.EnableRaisingEvents = true;

            Console.WriteLine("Press enter to exit.");
            Console.ReadLine();
        }

        private static void OnChanged(object sender, FileSystemEventArgs e)
        {
            if (e.ChangeType != WatcherChangeTypes.Changed)
            {
                return;
            }
            Console.WriteLine($"Изменение элемента: {e.FullPath}");
        }

        private static void OnCreated(object sender, FileSystemEventArgs e)
        {
            string value = $"Создан элемент: {e.FullPath}";
            Console.WriteLine(value);
        }

        private static void OnDeleted(object sender, FileSystemEventArgs e) =>
            Console.WriteLine($"Удален элемент: {e.FullPath}");

        private static void OnRenamed(object sender, RenamedEventArgs e)
        {
            Console.WriteLine($"Элемент переименован:");
            Console.WriteLine($"    Старое имя: {e.OldFullPath}");
            Console.WriteLine($"    Новое имя: {e.FullPath}");
        }

        private static void OnError(object sender, ErrorEventArgs e) =>
            PrintException(e.GetException());

        private static void PrintException(Exception? ex)
        {
            if (ex != null)
            {
                Console.WriteLine($"Сообщение об ошибке: {ex.Message}");
                Console.WriteLine("Трассировка стека:");
                Console.WriteLine(ex.StackTrace);
                Console.WriteLine();
                PrintException(ex.InnerException);
            }
        }
    }
}

Здесь стоит обратить внимание на следующие строки:

watcher.Filter = "*.*"; 
watcher.IncludeSubdirectories = true;

Таким образом мы указали, что отслеживаться будет абсолютно все элементы, включая вложенные папки. Результат работы программы может быть следующим:

Press enter to exit.
Элемент переименован:
Старое имя: c:\CSharp Output\MyFile.pdf
Новое имя: c:\CSharp Output\MyFile.txt
Удален элемент: c:\CSharp Output\MyFile.txt
Элемент переименован:
Старое имя: c:\CSharp Output\SubDir
Новое имя: c:\CSharp Output\Новая папка
Изменение элемента: c:\CSharp Output\Новая папка
Удален элемент: c:\CSharp Output\Новая папка

На что обратить внимание при работе с классом FileSystemWatcher

Все изменения, произошедшие в файловой системе хранятся во внутреннем буфере FileSystemWatcher максимальный размер которого может составлять 64 Кб. Таким образом, если за короткий промежуток времени произойдет большое количество изменений в отслеживаемом каталоге, то это может привести к переполнению буфера и, соответственно, вызову исключения. Чтобы попытаться избежать подобного исключения можно:

  1. Установить только те фильтры в свойстве NotifyFilter, которые вам действительно необходимы для работы
  2. Установить значение InternalBufferSize максимальным, если предполагается, что в каталоге может произойти большое количество изменений в течение короткого промежутка времени
  3. Делать обработчики событий у FileSystemWatcher  настолько короткими, насколько это возможно, чтобы не «забивать» буфер необработанными изменениями.

Итого

Сегодня мы рассмотрели основные возможности C# для отслеживания изменений в каталогах с использованием класса FileSystemWatcher . Этот класс позволяет отслеживать различные типы изменений файловой системы, включая как изменения, произошедшие в конкретном файле, так и изменения произошедшие во всех подкаталогах отслеживаемого каталога.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии