Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

161 lines
4.9 KiB

  1. //====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "stdafx.h"
  7. #include "FileChangeWatcher.h"
  8. #include "tier1/utldict.h"
  9. #include "filesystem_tools.h"
  10. #include "vstdlib/vstrtools.h"
  11. CFileChangeWatcher::CFileChangeWatcher()
  12. {
  13. m_pCallbacks = NULL;
  14. }
  15. CFileChangeWatcher::~CFileChangeWatcher()
  16. {
  17. Term();
  18. }
  19. void CFileChangeWatcher::Init( ICallbacks *pCallbacks )
  20. {
  21. Term();
  22. m_pCallbacks = pCallbacks;
  23. }
  24. bool CFileChangeWatcher::AddDirectory( const char *pSearchPathBase, const char *pDirName, bool bRecursive )
  25. {
  26. char fullDirName[MAX_PATH];
  27. V_ComposeFileName( pSearchPathBase, pDirName, fullDirName, sizeof( fullDirName ) );
  28. 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 );
  29. if ( hDir == INVALID_HANDLE_VALUE )
  30. {
  31. Warning( "CFileChangeWatcher::AddDirectory - can't get a handle to directory %s.\n", pDirName );
  32. return false;
  33. }
  34. // Call this once to start the ball rolling.. Next time we call it, it'll tell us the changes that
  35. // have happened since this call.
  36. CDirWatch *pDirWatch = new CDirWatch;
  37. V_strncpy( pDirWatch->m_SearchPathBase, pSearchPathBase, sizeof( pDirWatch->m_SearchPathBase ) );
  38. V_strncpy( pDirWatch->m_DirName, pDirName, sizeof( pDirWatch->m_DirName ) );
  39. V_strncpy( pDirWatch->m_FullDirName, fullDirName, sizeof( pDirWatch->m_FullDirName ) );
  40. pDirWatch->m_hDir = hDir;
  41. pDirWatch->m_hEvent = CreateEvent( NULL, false, false, NULL );
  42. memset( &pDirWatch->m_Overlapped, 0, sizeof( pDirWatch->m_Overlapped ) );
  43. pDirWatch->m_Overlapped.hEvent = pDirWatch->m_hEvent;
  44. if ( !CallReadDirectoryChanges( pDirWatch ) )
  45. {
  46. CloseHandle( pDirWatch->m_hEvent );
  47. CloseHandle( pDirWatch->m_hDir );
  48. delete pDirWatch;
  49. return false;
  50. }
  51. m_DirWatches.AddToTail( pDirWatch );
  52. return true;
  53. }
  54. void CFileChangeWatcher::Term()
  55. {
  56. for ( int i=0; i < m_DirWatches.Count(); i++ )
  57. {
  58. CloseHandle( m_DirWatches[i]->m_hDir );
  59. CloseHandle( m_DirWatches[i]->m_hEvent );
  60. }
  61. m_DirWatches.PurgeAndDeleteElements();
  62. m_pCallbacks = NULL;
  63. }
  64. int CFileChangeWatcher::Update()
  65. {
  66. CUtlDict< int, int > queuedChanges;
  67. int nTotalChanges = 0;
  68. // Check each CDirWatch.
  69. int i = 0;
  70. while ( i < m_DirWatches.Count() )
  71. {
  72. CDirWatch *pDirWatch = m_DirWatches[i];
  73. DWORD dwBytes = 0;
  74. if ( GetOverlappedResult( pDirWatch->m_hDir, &pDirWatch->m_Overlapped, &dwBytes, FALSE ) )
  75. {
  76. // Read through the notifications.
  77. int nBytesLeft = (int)dwBytes;
  78. char *pCurPos = pDirWatch->m_Buffer;
  79. while ( nBytesLeft >= sizeof( FILE_NOTIFY_INFORMATION ) )
  80. {
  81. FILE_NOTIFY_INFORMATION *pNotify = (FILE_NOTIFY_INFORMATION*)pCurPos;
  82. if ( m_pCallbacks )
  83. {
  84. // Figure out what happened to this file.
  85. WCHAR nullTerminated[2048];
  86. int nBytesToCopy = min( pNotify->FileNameLength, 2047 );
  87. memcpy( nullTerminated, pNotify->FileName, nBytesToCopy );
  88. nullTerminated[nBytesToCopy/2] = 0;
  89. char ansiFilename[1024];
  90. V_UnicodeToUTF8( nullTerminated, ansiFilename, sizeof( ansiFilename ) );
  91. // Now add it to the queue. We use this queue because sometimes Windows will give us multiple
  92. // of the same modified notification back to back, and we want to reduce redundant calls.
  93. int iExisting = queuedChanges.Find( ansiFilename );
  94. if ( iExisting == queuedChanges.InvalidIndex() )
  95. {
  96. iExisting = queuedChanges.Insert( ansiFilename, 0 );
  97. ++nTotalChanges;
  98. }
  99. }
  100. if ( pNotify->NextEntryOffset == 0 )
  101. break;
  102. pCurPos += pNotify->NextEntryOffset;
  103. nBytesLeft -= (int)pNotify->NextEntryOffset;
  104. }
  105. CallReadDirectoryChanges( pDirWatch );
  106. continue; // Check again because sometimes it queues up duplicate notifications.
  107. }
  108. // Process all the entries in the queue.
  109. for ( int iQueuedChange=queuedChanges.First(); iQueuedChange != queuedChanges.InvalidIndex(); iQueuedChange=queuedChanges.Next( iQueuedChange ) )
  110. {
  111. SendNotification( pDirWatch, queuedChanges.GetElementName( iQueuedChange ) );
  112. }
  113. queuedChanges.Purge();
  114. ++i;
  115. }
  116. return nTotalChanges;
  117. }
  118. void CFileChangeWatcher::SendNotification( CFileChangeWatcher::CDirWatch *pDirWatch, const char *pRelativeFilename )
  119. {
  120. // Use this for full filenames although you don't strictly need it..
  121. char fullFilename[MAX_PATH];
  122. V_ComposeFileName( pDirWatch->m_FullDirName, pRelativeFilename, fullFilename, sizeof( fullFilename ) );
  123. m_pCallbacks->OnFileChange( pRelativeFilename, fullFilename );
  124. }
  125. BOOL CFileChangeWatcher::CallReadDirectoryChanges( CFileChangeWatcher::CDirWatch *pDirWatch )
  126. {
  127. return ReadDirectoryChangesW( pDirWatch->m_hDir,
  128. pDirWatch->m_Buffer,
  129. sizeof( pDirWatch->m_Buffer ),
  130. true,
  131. FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE,
  132. NULL,
  133. &pDirWatch->m_Overlapped,
  134. NULL );
  135. }