|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "stdafx.h"
#include "FileChangeWatcher.h"
#include "tier1/utldict.h"
#include "filesystem_tools.h"
CFileChangeWatcher::CFileChangeWatcher() { m_pCallbacks = NULL; }
CFileChangeWatcher::~CFileChangeWatcher() { Term(); }
void CFileChangeWatcher::Init( ICallbacks *pCallbacks ) { Term(); m_pCallbacks = pCallbacks; }
bool CFileChangeWatcher::AddDirectory( const char *pSearchPathBase, const char *pDirName, bool bRecursive ) { char fullDirName[MAX_PATH]; V_ComposeFileName( pSearchPathBase, pDirName, fullDirName, sizeof( fullDirName ) ); HANDLE hDir = CreateFile( fullDirName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, NULL ); if ( hDir == INVALID_HANDLE_VALUE ) { Warning( "CFileChangeWatcher::AddDirectory - can't get a handle to directory %s.\n", pDirName ); return false; }
// Call this once to start the ball rolling.. Next time we call it, it'll tell us the changes that
// have happened since this call.
CDirWatch *pDirWatch = new CDirWatch; V_strncpy( pDirWatch->m_SearchPathBase, pSearchPathBase, sizeof( pDirWatch->m_SearchPathBase ) ); V_strncpy( pDirWatch->m_DirName, pDirName, sizeof( pDirWatch->m_DirName ) ); V_strncpy( pDirWatch->m_FullDirName, fullDirName, sizeof( pDirWatch->m_FullDirName ) ); pDirWatch->m_hDir = hDir; pDirWatch->m_hEvent = CreateEvent( NULL, false, false, NULL ); memset( &pDirWatch->m_Overlapped, 0, sizeof( pDirWatch->m_Overlapped ) ); pDirWatch->m_Overlapped.hEvent = pDirWatch->m_hEvent; if ( !CallReadDirectoryChanges( pDirWatch ) ) { CloseHandle( pDirWatch->m_hEvent ); CloseHandle( pDirWatch->m_hDir ); delete pDirWatch; return false; }
m_DirWatches.AddToTail( pDirWatch ); return true; }
void CFileChangeWatcher::Term() { for ( int i=0; i < m_DirWatches.Count(); i++ ) { CloseHandle( m_DirWatches[i]->m_hDir ); CloseHandle( m_DirWatches[i]->m_hEvent ); } m_DirWatches.PurgeAndDeleteElements(); m_pCallbacks = NULL; }
int CFileChangeWatcher::Update() { CUtlDict< int, int > queuedChanges; int nTotalChanges = 0; // Check each CDirWatch.
int i = 0; while ( i < m_DirWatches.Count() ) { CDirWatch *pDirWatch = m_DirWatches[i]; DWORD dwBytes = 0; if ( GetOverlappedResult( pDirWatch->m_hDir, &pDirWatch->m_Overlapped, &dwBytes, FALSE ) ) { // Read through the notifications.
int nBytesLeft = (int)dwBytes; char *pCurPos = pDirWatch->m_Buffer; while ( nBytesLeft >= sizeof( FILE_NOTIFY_INFORMATION ) ) { FILE_NOTIFY_INFORMATION *pNotify = (FILE_NOTIFY_INFORMATION*)pCurPos; if ( m_pCallbacks ) { // Figure out what happened to this file.
WCHAR nullTerminated[2048]; int nBytesToCopy = min( (int)pNotify->FileNameLength, 2047 ); memcpy( nullTerminated, pNotify->FileName, nBytesToCopy ); nullTerminated[nBytesToCopy/2] = 0; char ansiFilename[1024]; V_UnicodeToUTF8( nullTerminated, ansiFilename, sizeof( ansiFilename ) ); // Now add it to the queue. We use this queue because sometimes Windows will give us multiple
// of the same modified notification back to back, and we want to reduce redundant calls.
int iExisting = queuedChanges.Find( ansiFilename ); if ( iExisting == queuedChanges.InvalidIndex() ) { iExisting = queuedChanges.Insert( ansiFilename, 0 ); ++nTotalChanges; } } if ( pNotify->NextEntryOffset == 0 ) break; pCurPos += pNotify->NextEntryOffset; nBytesLeft -= (int)pNotify->NextEntryOffset; } CallReadDirectoryChanges( pDirWatch ); continue; // Check again because sometimes it queues up duplicate notifications.
}
// Process all the entries in the queue.
for ( int iQueuedChange=queuedChanges.First(); iQueuedChange != queuedChanges.InvalidIndex(); iQueuedChange=queuedChanges.Next( iQueuedChange ) ) { SendNotification( pDirWatch, queuedChanges.GetElementName( iQueuedChange ) ); } queuedChanges.Purge(); ++i; } return nTotalChanges; }
void CFileChangeWatcher::SendNotification( CFileChangeWatcher::CDirWatch *pDirWatch, const char *pRelativeFilename ) { // Use this for full filenames although you don't strictly need it..
char fullFilename[MAX_PATH]; V_ComposeFileName( pDirWatch->m_FullDirName, pRelativeFilename, fullFilename, sizeof( fullFilename ) );
m_pCallbacks->OnFileChange( pRelativeFilename, fullFilename ); }
BOOL CFileChangeWatcher::CallReadDirectoryChanges( CFileChangeWatcher::CDirWatch *pDirWatch ) { return ReadDirectoryChangesW( pDirWatch->m_hDir, pDirWatch->m_Buffer, sizeof( pDirWatch->m_Buffer ), true, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, &pDirWatch->m_Overlapped, NULL ); }
|