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