2014-03-14 14:13:41 -04:00
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
# include "DirectoryWatcherPrivatePCH.h"
FDirectoryWatchRequestWindows : : FDirectoryWatchRequestWindows ( )
{
bPendingDelete = false ;
bEndWatchRequestInvoked = false ;
MaxChanges = 16384 ;
bWatchSubtree = true ;
NotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION ;
DirectoryHandle = INVALID_HANDLE_VALUE ;
BufferLength = sizeof ( FILE_NOTIFY_INFORMATION ) * MaxChanges ;
Buffer = new uint8 [ BufferLength ] ;
BackBuffer = new uint8 [ BufferLength ] ;
FMemory : : Memzero ( & Overlapped , sizeof ( Overlapped ) ) ;
FMemory : : Memzero ( Buffer , BufferLength ) ;
Overlapped . hEvent = this ;
}
FDirectoryWatchRequestWindows : : ~ FDirectoryWatchRequestWindows ( )
{
if ( Buffer )
{
delete Buffer ;
}
if ( BackBuffer )
{
delete BackBuffer ;
}
if ( DirectoryHandle ! = INVALID_HANDLE_VALUE )
{
: : CloseHandle ( DirectoryHandle ) ;
DirectoryHandle = INVALID_HANDLE_VALUE ;
}
}
bool FDirectoryWatchRequestWindows : : Init ( const FString & InDirectory )
{
check ( Buffer ) ;
if ( InDirectory . Len ( ) = = 0 )
{
// Verify input
return false ;
}
Directory = InDirectory ;
if ( DirectoryHandle ! = INVALID_HANDLE_VALUE )
{
// If we already had a handle for any reason, close the old handle
: : CloseHandle ( DirectoryHandle ) ;
}
// Make sure the path is absolute
const FString FullPath = FPaths : : ConvertRelativePathToFull ( Directory ) ;
// Get a handle to the directory with FILE_FLAG_BACKUP_SEMANTICS as per remarks for ReadDirectoryChanges on MSDN
DirectoryHandle = : : CreateFile (
* FullPath ,
FILE_LIST_DIRECTORY ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ,
NULL ,
OPEN_EXISTING ,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED ,
NULL
) ;
if ( DirectoryHandle = = INVALID_HANDLE_VALUE )
{
// Failed to obtain a handle to this directory
return false ;
}
const bool bSuccess = ! ! : : ReadDirectoryChangesW (
DirectoryHandle ,
Buffer ,
BufferLength ,
bWatchSubtree ,
NotifyFilter ,
NULL ,
& Overlapped ,
& FDirectoryWatchRequestWindows : : ChangeNotification ) ;
if ( ! bSuccess )
{
: : CloseHandle ( DirectoryHandle ) ;
return false ;
}
return true ;
}
void FDirectoryWatchRequestWindows : : AddDelegate ( const IDirectoryWatcher : : FDirectoryChanged & InDelegate )
{
Delegates . Add ( InDelegate ) ;
}
bool FDirectoryWatchRequestWindows : : RemoveDelegate ( const IDirectoryWatcher : : FDirectoryChanged & InDelegate )
{
if ( Delegates . Contains ( InDelegate ) )
{
Delegates . Remove ( InDelegate ) ;
return true ;
}
else
{
return false ;
}
}
bool FDirectoryWatchRequestWindows : : HasDelegates ( ) const
{
return Delegates . Num ( ) > 0 ;
}
HANDLE FDirectoryWatchRequestWindows : : GetDirectoryHandle ( ) const
{
return DirectoryHandle ;
}
void FDirectoryWatchRequestWindows : : EndWatchRequest ( )
{
if ( ! bEndWatchRequestInvoked & & ! bPendingDelete )
{
if ( DirectoryHandle ! = INVALID_HANDLE_VALUE )
{
# if WINVER >= 0x600 // CancelIoEx() is only supported on Windows Vista and higher
CancelIoEx ( DirectoryHandle , & Overlapped ) ;
# else
CancelIo ( DirectoryHandle ) ;
# endif
2014-05-09 11:52:31 -04:00
// Wait for cancel operation to finish before closing the handle
WaitForSingleObjectEx ( DirectoryHandle , 1000 , true ) ;
2014-03-14 14:13:41 -04:00
: : CloseHandle ( DirectoryHandle ) ;
DirectoryHandle = INVALID_HANDLE_VALUE ;
}
else
{
// The directory handle was never opened
bPendingDelete = true ;
}
// Only allow this to be invoked once
bEndWatchRequestInvoked = true ;
}
}
void FDirectoryWatchRequestWindows : : ProcessPendingNotifications ( )
{
// Trigger all listening delegates with the files that have changed
if ( FileChanges . Num ( ) > 0 )
{
for ( int32 DelegateIdx = 0 ; DelegateIdx < Delegates . Num ( ) ; + + DelegateIdx )
{
Delegates [ DelegateIdx ] . Execute ( FileChanges ) ;
}
FileChanges . Empty ( ) ;
}
}
void FDirectoryWatchRequestWindows : : ProcessChange ( uint32 Error , uint32 NumBytes )
{
if ( Error = = ERROR_OPERATION_ABORTED | | NumBytes = = 0 )
{
// The operation was aborted, likely due to EndWatchRequest canceling it.
// Mark the request for delete so it can be cleaned up next tick.
bPendingDelete = true ;
return ;
}
bool bValidNotification = ( Error ! = ERROR_OPERATION_ABORTED & & Error ! = ERROR_IO_INCOMPLETE & & NumBytes > 0 ) ;
// Copy the change to the backbuffer so we can start a new read as soon as possible
if ( bValidNotification )
{
check ( BackBuffer ) ;
FMemory : : Memcpy ( BackBuffer , Buffer , NumBytes ) ;
}
// Start up another read
const bool bSuccess = ! ! : : ReadDirectoryChangesW (
DirectoryHandle ,
Buffer ,
BufferLength ,
bWatchSubtree ,
NotifyFilter ,
NULL ,
& Overlapped ,
& FDirectoryWatchRequestWindows : : ChangeNotification ) ;
if ( ! bValidNotification )
{
UE_LOG ( LogDirectoryWatcher , Warning , TEXT ( " A directory notification failed for '%s' because it was aborted or there was a buffer overflow. " ) , * Directory ) ;
return ;
}
// No need to process the change if we can not execute any delegates
if ( ! HasDelegates ( ) )
{
return ;
}
// Process the change
uint8 * InfoBase = BackBuffer ;
do
{
FILE_NOTIFY_INFORMATION * NotifyInfo = ( FILE_NOTIFY_INFORMATION * ) InfoBase ;
// Copy the WCHAR out of the NotifyInfo so we can put a NULL terminator on it and convert it to a FString
const int32 Len = NotifyInfo - > FileNameLength / sizeof ( WCHAR ) ;
WCHAR * RawFilename = new WCHAR [ Len + 1 ] ;
FMemory : : Memcpy ( RawFilename , NotifyInfo - > FileName , NotifyInfo - > FileNameLength ) ;
RawFilename [ Len ] = 0 ;
FFileChangeData : : EFileChangeAction Action ;
switch ( NotifyInfo - > Action )
{
case FILE_ACTION_ADDED :
case FILE_ACTION_RENAMED_NEW_NAME :
Action = FFileChangeData : : FCA_Added ;
break ;
case FILE_ACTION_REMOVED :
case FILE_ACTION_RENAMED_OLD_NAME :
Action = FFileChangeData : : FCA_Removed ;
break ;
case FILE_ACTION_MODIFIED :
Action = FFileChangeData : : FCA_Modified ;
break ;
default :
Action = FFileChangeData : : FCA_Unknown ;
break ;
}
// WCHAR to TCHAR conversion. In windows this is probably okay.
const FString Filename = Directory / FString ( RawFilename ) ;
new ( FileChanges ) FFileChangeData ( Filename , Action ) ;
// Delete the scratch WCHAR*
delete [ ] RawFilename ;
// If there is not another entry, break the loop
if ( NotifyInfo - > NextEntryOffset = = 0 )
{
break ;
}
// Adjust the offset and update the NotifyInfo pointer
InfoBase + = NotifyInfo - > NextEntryOffset ;
}
while ( true ) ;
}
void FDirectoryWatchRequestWindows : : ChangeNotification ( : : DWORD Error , : : DWORD NumBytes , LPOVERLAPPED InOverlapped )
{
FDirectoryWatchRequestWindows * Request = ( FDirectoryWatchRequestWindows * ) InOverlapped - > hEvent ;
check ( Request ) ;
Request - > ProcessChange ( ( uint32 ) Error , ( uint32 ) NumBytes ) ;
}