FileSystemWatcher.h
#pragma once
#pragma execution_character_set("utf-8")
#include <Windows.h>
#include <QThread>
//观察者线程声明
class WatcherThread;
/*
* @class FileSystemWatcher,文件系统观察者
*/
class FileSystemWatcher : public QObject
{
Q_OBJECT
public:
/*
* @FileSystemWatcher,构造[重载1]
* @param1,父对象
*/
FileSystemWatcher(QObject* parent = nullptr);
/*
* @FileSystemWatcher,构造[重载2]
* @param1,要监控的路径名列表
* @param2,要监控的路径,是否监控其包含的子路径
*/
FileSystemWatcher(const QStringList& pathNameList, const QList<bool>& monitorSubpathList = QList<bool>(), QObject* parent = nullptr);
/*
* @~FileSystemWatcher(),析构
*/
~FileSystemWatcher();
/*
* @getLastError,获取最终错误
* @return,const QString&
*/
const QString& getLastError();
/*
* @setMonitorPath,设置监控路径
* @param1,要监控的路径名列表
* @param2,要监控的路径,是否监控其包含的子路径
*/
void setMonitorPath(const QStringList& pathNameList, const QList<bool>& monitorSubpathList = QList<bool>());
/*
* @startMonitor,开始监控
* @return,bool
*/
bool startMonitor();
/*
* @stopMonitor,停止监控
* @return,void
*/
void stopMonitor();
/*
* @restartMonitor,重新开始监控
* @return,bool
*/
bool restartMonitor();
/*
* @getPathNameList,获取路径名列表
* @return,const QStringList&
*/
const QStringList& getPathNameList();
/*
* @getMonitorSubpathList,获取监控子路径列表
* @return,const QList<bool>&
*/
const QList<bool>& getMonitorSubpathList();
protected:
/*
* @setLastError,设置最终错误
* @param1,错误信息
* @return,void
*/
void setLastError(const QString& error);
private slots:
/*
* @fileChangedSlot,文件改变槽
* @param1,文件改变动作
* @param2,文件名
* @return,void
*/
void fileChangedSlot(ulong action, const QString& fileName);
/*
* @threadErrorSlot,线程错误槽
* @param1,错误信息
* @return,void
*/
void threadErrorSlot(const QString& error);
signals:
/*
* @fileChangedSignal,文件改变信号
* @param1,文件改变动作,动作包含
* FILE_ACTION_ADDED,
* FILE_ACTION_REMOVED,
* FILE_ACTION_MODIFIED,
* FILE_ACTION_RENAMED_OLD_NAME,
* FILE_ACTION_RENAMED_NEW_NAME
* @param2,文件名
* @return,void
*/
void fileChangedSignal(ulong action, const QString& fileName);
/*
* @threadErrorSignal,线程错误信号
* @param1,错误信息
* @return,void
*/
void threadErrorSignal(const QString& error);
private:
//路径名列表
QStringList m_pathNameList;
//监控子路径列表
QList<bool> m_monitorSubpathList;
//最终错误
QString m_lastError = "未知错误";
//线程列表
QList<WatcherThread*> m_threadList;
};
/*
* @class WatcherThread,观察者线程
*/
class WatcherThread : public QThread {
Q_OBJECT
public:
/*
* @WatcherThread,构造[重载1]
* @param1,父对象
*/
WatcherThread(QObject* parent = nullptr);
/*
* @WatcherThread,构造[重载2]
* @param1,路径句柄
* @param2,路径名
* @param3,是否监控子路径
*/
WatcherThread(HANDLE handle, const QString& pathName, bool monitorSubpath);
/*
* @~WatcherThread,析构
*/
~WatcherThread();
/*
* @setPath,设置路径
* @param1,路径名
* @param2,是否监控子路径
* @return,void
*/
void setPath(const QString& pathName, bool monitorSubpath);
/*
* @setHandle,设置句柄
* @param1,句柄
* @return,void
*/
void setHandle(HANDLE handle);
/*
* @startThread,开启线程
* @return,void
*/
void startThread();
/*
* @stopThread,停止线程
* @return,void
*/
void stopThread();
protected:
/*
* @run,重写父类QThread::run
* @return,void
*/
virtual void run() override;
private:
//监控路径句柄
HANDLE m_handle = INVALID_HANDLE_VALUE;
//控制线程退出事件
HANDLE m_event = nullptr;
//路径名
QString m_pathName;
//是否监控子路径
bool m_monitorSubpath = true;
signals:
/*
* @fileChangedSignal,文件改变信号
* @param1,文件改变动作
* @param2,文件名
* @return,void
*/
void fileChangedSignal(ulong action, const QString& fileName);
/*
* @threadErrorSignal,线程错误信号
* @param1,错误信息
* @return,void
*/
void threadErrorSignal(const QString& error);
};
FileSystemWatcher.cpp
#include "FileSystemWatcher.h"
FileSystemWatcher::FileSystemWatcher(QObject* parent)
{
}
FileSystemWatcher::FileSystemWatcher(const QStringList& pathNameList, const QList<bool>& monitorSubpathList, QObject* parent)
{
setMonitorPath(pathNameList, monitorSubpathList);
}
FileSystemWatcher::~FileSystemWatcher()
{
}
const QString& FileSystemWatcher::getLastError()
{
return m_lastError;
}
void FileSystemWatcher::setMonitorPath(const QStringList& pathNameList, const QList<bool>& monitorSubpathList)
{
m_pathNameList = pathNameList;
for (auto& x : m_pathNameList)
{
const QString& c = x.right(1);
if (c != "\\" && c != "/")
{
x.append("\\");
}
}
m_monitorSubpathList = monitorSubpathList;
while (m_monitorSubpathList.size() < m_pathNameList.size())
{
m_monitorSubpathList.append(true);
}
}
bool FileSystemWatcher::startMonitor()
{
bool result = false, success = true;
do
{
QList<HANDLE> handleList;
for (const auto& x : m_pathNameList)
{
HANDLE handle = CreateFileW(x.toStdWString().c_str(),
GENERIC_READ | GENERIC_WRITE | FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
nullptr);
if (handle == INVALID_HANDLE_VALUE)
{
setLastError(QString("获取%1路径句柄失败,错误代码%2").arg(x).arg(::GetLastError()));
success = false;
break;
}
handleList.append(handle);
}
if (!success)
{
for (const auto& x : handleList)
{
CloseHandle(x);
}
break;
}
for (int i = 0; i < handleList.size(); ++i)
{
WatcherThread* thread = new(std::nothrow) WatcherThread(this);
if (!thread)
{
setLastError(QString("创建监控%1线程失败").arg(m_pathNameList[i]));
break;
}
connect(thread, &WatcherThread::fileChangedSignal, this, &FileSystemWatcher::fileChangedSlot);
connect(thread, &WatcherThread::threadErrorSignal, this, &FileSystemWatcher::threadErrorSlot);
m_threadList.append(thread);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->setPath(m_pathNameList[i], m_monitorSubpathList[i]);
thread->setHandle(handleList[i]);
thread->startThread();
}
result = true;
} while (false);
return result;
}
void FileSystemWatcher::stopMonitor()
{
for (const auto& x : m_threadList)
{
x->stopThread();
}
m_threadList.clear();
}
bool FileSystemWatcher::restartMonitor()
{
stopMonitor();
return startMonitor();
}
const QStringList& FileSystemWatcher::getPathNameList()
{
return m_pathNameList;
}
const QList<bool>& FileSystemWatcher::getMonitorSubpathList()
{
return m_monitorSubpathList;
}
void FileSystemWatcher::setLastError(const QString& error)
{
m_lastError = error;
}
void FileSystemWatcher::threadErrorSlot(const QString& error)
{
emit threadErrorSignal(error);
}
void FileSystemWatcher::fileChangedSlot(ulong action, const QString& fileName)
{
emit fileChangedSignal(action, fileName);
}
WatcherThread::WatcherThread(QObject* parent)
:QThread(parent)
{
}
WatcherThread::WatcherThread(HANDLE handle, const QString& pathName, bool monitorSubpath)
{
setHandle(handle);
setPath(pathName, monitorSubpath);
}
WatcherThread::~WatcherThread()
{
stopThread();
if (isRunning())
{
wait(3000);
}
}
void WatcherThread::setPath(const QString& pathName, bool monitorSubpath)
{
m_pathName = pathName;
m_monitorSubpath = monitorSubpath;
}
void WatcherThread::setHandle(HANDLE handle)
{
m_handle = handle;
}
void WatcherThread::startThread()
{
this->start();
}
void WatcherThread::stopThread()
{
if (!isRunning())
return;
CancelIoEx(m_handle, nullptr);
SetEvent(m_event);
}
void WatcherThread::run()
{
uchar buffer[1024] = { 0 };
ulong bytes = 0, offset = 0, error = 0;
m_event = CreateEventW(nullptr, true, false, nullptr);
OVERLAPPED lapped;
lapped.hEvent = CreateEventW(nullptr, true, false, nullptr);
if (!m_event || !lapped.hEvent)
{
emit threadErrorSignal(QString("创建路径%1事件失败,错误代码%2")
.arg(m_pathName).arg(::GetLastError()));
}
else
{
do
{
memset(buffer, 0, sizeof(buffer));
if (ReadDirectoryChangesW(m_handle, buffer, sizeof(buffer), m_monitorSubpath,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
&bytes, &lapped, nullptr))
{
if (GetOverlappedResult(m_handle, &lapped, &bytes, TRUE))
{
FILE_NOTIFY_INFORMATION* info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer);
do
{
QString fileName = m_pathName + QString::fromWCharArray(info->FileName, info->FileNameLength / sizeof(wchar_t));
emit fileChangedSignal(info->Action, fileName);
offset = info->NextEntryOffset;
info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<uchar*>(info) + offset);
} while (offset);
}
else
{
break;
}
if (!ResetEvent(lapped.hEvent))
{
break;
}
}
else
{
emit threadErrorSignal(QString("监控路径%1文件变化失败,错误代码%2")
.arg(m_pathName).arg(::GetLastError()));
}
error = WaitForSingleObject(m_event, 0);
} while (error == WAIT_TIMEOUT);
CloseHandle(lapped.hEvent);
CloseHandle(m_handle);
CloseHandle(m_event);
}
quit();
}
参考网址:异步readdirectorychangesw调用阻止线程退出 - IT屋-程序员软件开发技术分享社区
不建议使用该类库,推荐文件监控类库: