Team Fortress 2 Source Code as on 22/4/2020
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.

160 lines
4.9 KiB

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