📄 directorychanges.cpp
字号:
// DirectoryChanges.cpp: implementation of the CDirectoryChangeWatcher and CDirectoryChangeHandler classes.
//
///////////////////////////////////////////////////////////////////
///
/***********************************************************
Author: Wes Jones wesj@hotmail.com
File: DirectoryChanges.cpp
Latest Changes:
11/22/2001 -- Fixed bug causing file name's to be truncated if
longer than 130 characters. Fixed CFileNotifyInformation::GetFileName()
Thanks to Edric(uo_edric@hotmail.com) for pointing this out.
Added code to enable process privileges when CDirectoryChangeWatcher::WatchDirectory()
is first called. See docuementation API for ReadDirectoryChangesW() for more information of required privileges.
Currently enables these privileges: (11/22/2001)
SE_BACKUP_NAME
SE_CHANGE_NOTIFY_NAME
SE_RESTORE_NAME(02/09/2002)
Implemented w/ helper class CPrivilegeEnabler.
11/23/2001 Added classes so that all notifications are handled by the
same thread that called CDirectoryChangeWatcher::WatchDirectory(),
ie: the main thread.
CDirectoryChangeHandler::On_Filexxxx() functions are now called in the
context of the main thread instead of the worker thread.
This is good for two reasons:
1: The worker thread spends less time handling each notification.
The worker thread simply passes the notification to the main thread,
which does the processing.
This means that each file change notification is handled as fast as possible
and ReadDirectoryChangesW() can be called again to receive more notifications
faster.
2: This makes it easier to make an ActiveX or ATL object with this class
because the events that are fired, fire in the context of the main thread.
The fact that I'm using a worker thread w/ this class is totally
transparent to a client user.
If I decide to turn this app into an ActiveX or ATL object
I don't have to worry about wierd COM rules and multithreading issues,
and neither does the client, be the client a VB app, C++ app, Delphi app, or whatever.
Implemented with CDelayedDirectoryChangeHandler in DirectoryChangeHandler.h/.cpp
02/06/2002 Fixed a bug that would cause an application to hang.
If ReadDirectoryChangesW was to fail during normal operation,
the short story is that the application would hang
when it called CDirectoryChangeWatcher::UnwatchDirectory(const CString & )
One way to reproduce this behavior on the old code
is to watch a directory using a UNC path, and then change the IP
address of that machine while the watch was running. Exitting
the app after this would cause it to hang.
Steps to reproduce it:
1) Assume that the computer running the code is
named 'ThisComputer' and there is a shared folder named 'FolderName'
2) Start a watch on a folder using a UNC path: ie: \\ThisComputer\FolderName
eg: CDirectoryChangeWatcher watcher;
watcher.WatchDirectory(_T("\\\\ThisComputer\\FolderName",/ * other parameters * /)
3) Change the IP address of 'ThisComputer'
** ReadDirectoryChangesW() will fail some point after this.
4) Exit the application... it may hang.
Anyways, that's what the bug fix is for.
02/06/2002 New side effects for CDirectoryChangeHandler::On_ReadDirectoryChangeError()
If CDirectoryChangeHandler::On_ReadDirectoryChangeError() is ever executed
the directory that you are watching will have been unwatched automatically due
to the error condition.
A call to CDirectoryChangeWatcher::IsWatchingDirectory() will fail because the directory
is no longer being watched. You'll need to re-watch that directory.
02/09/2002 Added a parameter to CDirectoryChangeHandler::On_ReadDirectoryChangeError()
Added the parameter: const CString & strDirectoryName
The new signature is now:
virtual void CDirectoryChangeHandler::On_ReadDirectoryChangeError(DWORD dwError, const CString & strDirectoryName);
This new parameter gives you the name of the directory that the error occurred on.
04/25/2002 Provided a way to get around the requirement of a message pump.
A console app can now use this w/out problems by passing false
to the constructor of CDirectoryChangeWatcher.
An app w/ a message pump can also pass false if it so desires w/out problems.
04/25/2002 Added two new virtual functions to CDirectoryChangeHandler
Added:
On_WatchStarted(DWORD dwError, const CString & strDirectoryName)
On_WatchStopped(const CString & strDirectoryName);
See header file for details.
04/27/2002 Added new function to CDirectoryChangeHandler:
Added virtual bool On_FilterNotification(DWORD dwNotifyAction, LPCTSTR szFileName, LPCTSTR szNewFileName)
This function is called before any notification function, and allows the
CDirectoryChangeHandler derived class to ignore file notifications
by performing a programmer defined test.
By ignore, i mean that
On_FileAdded(), On_FileRemoved(), On_FileModified(), or On_FileNameChanged()
will NOT be called if this function returns false.
The default implementation always returns true, signifying that ALL notifications
are to be called.
04/27/2002 Added new Parameters to CDirectoryChangeWatcher::WatchDirectory()
The new parameters are:
LPCTSTR szIncludeFilter
LPCTSTR szExcludeFilter
Both parameters are defaulted to NULL.
Signature is now:
CDirectoryChangeWatcher::WatchDirectory(const CString & strDirToWatch,
DWORD dwChangesToWatchFor,
CDirectoryChangeHandler * pChangeHandler,
BOOL bWatchSubDirs = FALSE,
LPCTSTR szIncludeFilter = NULL,
LPCTSTR szExcludeFilter = NULL)
04/27/2002 Added support for include and exclude filters.
These filters allow you to receive notifications for just the files you
want... ie: you can specify that you only want to receive notifications
for changes to "*.txt" files or some other such file filter.
NOTE: This feature is implemented w/ the PathMatchSpec() api function
which is only available on NT4.0 if Internet Explorer 4.0 or above is installed.
See MSDN for PathMatchSpec(). Win2000, and XP do not have to worry about it.
Filter specifications:
Accepts wild card characters * and ?, just as you're used to for the DOS dir command.
It is possible to specify multiple extenstions in the filter by separating each filter spec
with a semi-colon.
eg: "*.txt;*.tmp;*.log" <-- this filter specifies all .txt, .tmp, & .log files
Filters are passed as parameters to CDirectoryChangeWatcher::WatchDirectory()
NOTE: The filters you specify take precedence over CDirectoryChangeHandler::On_FilterNotification().
This means that if the file name does not pass the filters that you specify
when the watch is started, On_FilterNotification() will not be called.
Filter specifications are case insensitive, ie: ".tmp" and ".TMP" are the same
FILTER TYPES:
Include Filter:
If you specify an include filter, you are specifying that you
only want to receive notifications for specific file types.
eg: "*.log" means that you only want notifications for changes
to files w/ an exention of ".log".
Changes to ALL OTHER other file types are ignored.
An empty, or not specified include filter means that you want
notifications for changes of ALL file types.
Exclude filter:
If you specify an exclude filter, you are specifying that
you do not wish to receive notifications for a specific type of file or files.
eg: "*.tmp" would mean that you do not want any notifications
regarding files that end w/ ".tmp"
An empty, or not specified exclude filter means that
you do not wish to exclude any file notifications.
************************************************************/
#include "stdafx.h"
#include "DirectoryChanges.h"
#include "DelayedDirectoryChangeHandler.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//
//
// Fwd Declares & #define's
//
//
//
// Helper classes
class CPrivilegeEnabler; //for use w/ enabling process priveledges when this code is first used. It's a singleton.
class CFileNotifyInformation;//helps CDirectoryChangeWatcher class notify CDirectoryChangeHandler class of changes to files.
class CDelayedDirectoryChangeHandler; // Helps all notifications become handled by the main
// thread, or a worker thread depending upon a value passed to the
// constructor of CDirectoryChangeWatcher.
//
// For notifications to fire in the main thread, your app must have a message pump.
//
// The 'main' thread is the one that called CDirectoryChangeWatcher::WatchDirectory()
// Helper functions
static BOOL EnablePrivilege(LPCTSTR pszPrivName, BOOL fEnable = TRUE);
static bool IsDirectory(const CString & strPath);
/////////////////////////////////////////////////////////////////////
// Helper functions.
BOOL EnablePrivilege(LPCTSTR pszPrivName, BOOL fEnable /*= TRUE*/)
//
// I think this code is from a Jeffrey Richter book...
//
// Enables user priviledges to be set for this process.
//
// Process needs to have access to certain priviledges in order
// to use the ReadDirectoryChangesW() API. See documentation.
{
BOOL fOk = FALSE;
// Assume function fails
HANDLE hToken;
// Try to open this process's access token
if (OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES, &hToken))
{
// privilege
TOKEN_PRIVILEGES tp = { 1 };
if( LookupPrivilegeValue(NULL, pszPrivName, &tp.Privileges[0].Luid) )
{
tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
AdjustTokenPrivileges(hToken, FALSE, &tp,
sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS);
}
CloseHandle(hToken);
}
return(fOk);
}
static bool IsDirectory(const CString & strPath)
//
// Returns: bool
// true if strPath is a path to a directory
// false otherwise.
//
{
DWORD dwAttrib = GetFileAttributes( strPath );
return static_cast<bool>( ( dwAttrib != 0xffffffff
&& (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) );
}
///////////////////////////////////////////////
//Helper class:
class CFileNotifyInformation
/*******************************
A Class to more easily traverse the FILE_NOTIFY_INFORMATION records returned
by ReadDirectoryChangesW().
FILE_NOTIFY_INFORMATION is defined in Winnt.h as:
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset;
DWORD Action;
DWORD FileNameLength;
WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
ReadDirectoryChangesW basically puts x amount of these records in a
buffer that you specify.
The FILE_NOTIFY_INFORMATION structure is a 'dynamically sized' structure (size depends on length
of the file name (+ sizeof the DWORDs in the struct))
Because each structure contains an offset to the 'next' file notification
it is basically a singly linked list. This class treats the structure in that way.
Sample Usage:
BYTE Read_Buffer[ 4096 ];
...
ReadDirectoryChangesW(...Read_Buffer, 4096,...);
...
CFileNotifyInformation notify_info( Read_Buffer, 4096);
do{
switch( notify_info.GetAction() )
{
case xx:
notify_info.GetFileName();
}
while( notify_info.GetNextNotifyInformation() );
********************************/
{
public:
CFileNotifyInformation( BYTE * lpFileNotifyInfoBuffer, DWORD dwBuffSize)
: m_pBuffer( lpFileNotifyInfoBuffer ),
m_dwBufferSize( dwBuffSize )
{
ASSERT( lpFileNotifyInfoBuffer && dwBuffSize );
m_pCurrentRecord = (PFILE_NOTIFY_INFORMATION) m_pBuffer;
}
BOOL GetNextNotifyInformation();
BOOL CopyCurrentRecordToBeginningOfBuffer(OUT DWORD & ref_dwSizeOfCurrentRecord);
DWORD GetAction() const;//gets the type of file change notifiation
CString GetFileName()const;//gets the file name from the FILE_NOTIFY_INFORMATION record
CString GetFileNameWithPath(const CString & strRootPath) const;//same as GetFileName() only it prefixes the strRootPath into the file name
protected:
BYTE * m_pBuffer;//<--all of the FILE_NOTIFY_INFORMATION records 'live' in the buffer this points to...
DWORD m_dwBufferSize;
PFILE_NOTIFY_INFORMATION m_pCurrentRecord;//this points to the current FILE_NOTIFY_INFORMATION record in m_pBuffer
};
BOOL CFileNotifyInformation::GetNextNotifyInformation()
/***************
Sets the m_pCurrentRecord to the next FILE_NOTIFY_INFORMATION record.
Even if this return FALSE, (unless m_pCurrentRecord is NULL)
m_pCurrentRecord will still point to the last record in the buffer.
****************/
{
if( m_pCurrentRecord
&& m_pCurrentRecord->NextEntryOffset != 0UL)//is there another record after this one?
{
//set the current record to point to the 'next' record
PFILE_NOTIFY_INFORMATION pOld = m_pCurrentRecord;
m_pCurrentRecord = (PFILE_NOTIFY_INFORMATION) ((LPBYTE)m_pCurrentRecord + m_pCurrentRecord->NextEntryOffset);
ASSERT( (DWORD)((BYTE*)m_pCurrentRecord - m_pBuffer) < m_dwBufferSize);//make sure we haven't gone too far
if( (DWORD)((BYTE*)m_pCurrentRecord - m_pBuffer) > m_dwBufferSize )
{
//we've gone too far.... this data is hosed.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -