source-engine/hammer/FileChangeWatcher.cpp

161 lines
4.9 KiB
C++
Raw Permalink Normal View History

2020-04-22 16:56:21 +00:00
//========= 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 );
}