Работа с файлами и директориями в Qt

QFileSystemWatcher: отслеживание изменений файлов и папок в реальном времени

Введение

Многим приложениям нужно знать, когда изменился файл конфигурации, появился новый лог или была удалена папка с данными. Опрашивать файловую систему вручную через таймер — неэффективно и медленно реагирует на события. Qt предлагает готовый класс QFileSystemWatcher, который использует системные механизмы уведомлений (inotify в Linux, ReadDirectoryChangesW в Windows, FSEvents в macOS) и сообщает об изменениях практически мгновенно.

Концепция

QFileSystemWatcher умеет следить за двумя типами объектов:

  • Файлами — сигнал fileChanged срабатывает при изменении содержимого, прав доступа или удалении файла.
  • Директориями — сигнал directoryChanged срабатывает при добавлении, удалении или переименовании файлов внутри папки (но не при изменении содержимого вложенных файлов — для этого их нужно добавлять отдельно).

Важно понимать: QFileSystemWatcher не отслеживает изменения рекурсивно. Если нужно следить за поддиректориями, их нужно добавлять явно, обходя дерево.

Пример кода: QFileSystemWatcher

#include <QCoreApplication>
#include <QFileSystemWatcher>
#include <QDebug>
#include <QDir>
#include <QFile>

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    QFileSystemWatcher watcher;

    // Следим за конкретным файлом
    const QString configPath = "config.ini";
    if (!QFile::exists(configPath)) {
        QFile f(configPath);
        f.open(QIODevice::WriteOnly);
        f.write("[general]\nvalue=1\n");
    }
    watcher.addPath(configPath);

    // Следим за директорией
    const QString dirPath = QDir::currentPath();
    watcher.addPath(dirPath);

    QObject::connect(&watcher, &QFileSystemWatcher::fileChanged,
                      [](const QString &path) {
        qDebug() << "Файл изменён:" << path;

        // На некоторых ОС после изменения файл "выпадает" из списка
        // наблюдения (например, если редактор пересоздаёт файл).
        // Поэтому путь полезно перерегистрировать.
        if (QFile::exists(path)) {
            // повторная подписка делается из того же watcher
        }
    });

    QObject::connect(&watcher, &QFileSystemWatcher::directoryChanged,
                      [](const QString &path) {
        qDebug() << "Содержимое директории изменилось:" << path;
    });

    qDebug() << "Наблюдение запущено. Измените config.ini для проверки.";

    return app.exec();
}

Пояснения к коду

Метод addPath() добавляет один путь, а addPaths() — список путей за один вызов, что эффективнее при большом количестве файлов. Текущий список наблюдаемых объектов можно получить через files() и directories().

Сигналы fileChanged и directoryChanged передают путь, который изменился, что позволяет обрабатывать события для нескольких объектов одним слотом.

Подводные камни

  1. Потеря подписки после пересоздания файла. Многие редакторы и системы сборки не изменяют файл «на месте», а удаляют старый и создают новый inode. После такой операции QFileSystemWatcher теряет файл из списка наблюдения, и fileChanged больше не сработает. Решение — в обработчике fileChanged проверять QFile::exists() и вызывать addPath() повторно.
  1. Лимиты на количество наблюдаемых inotify-объектов в Linux. При наблюдении за тысячами файлов можно упереться в системный лимит fs.inotify.max_user_watches. Для больших деревьев лучше следить за директориями верхнего уровня, а не за каждым файлом.
  1. Задержка событий и дублирование сигналов. Некоторые операции (например, сохранение файла текстовым редактором через временный файл и rename) могут генерировать несколько сигналов подряд. Рекомендуется использовать QTimer с небольшим debounce-интервалом (200–500 мс), чтобы реагировать только на последнее событие в серии.
  1. Сетевые и виртуальные файловые системы. На сетевых дисках (SMB/NFS) уведомления могут не приходить вовсе или приходить с большой задержкой — в этом случае надёжнее периодическая проверка QFileInfo::lastModified().
  1. Рекурсивное наблюдение не поддерживается из коробки — его нужно реализовывать самостоятельно через QDirIterator, добавляя новые подкаталоги в watcher по мере их появления.

QFileSystemWatcher — простой и эффективный инструмент для большинства задач мониторинга, но для промышленных систем с высокими требованиями к надёжности стоит комбинировать его с периодической самопроверкой состояния.

================================================================================