A modded EditSaber for making beat saber maps.
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.
 
 
 
 

414 lines
12 KiB

#include "WindowsFileUtilityPrivatePCH.h"
#include "WFUFolderWatchInterface.h"
#include "WFUFileListInterface.h"
#include "WindowsFileUtilityFunctionLibrary.h"
//static TMAP definition
TMap<FString, TArray<FWatcher>> UWindowsFileUtilityFunctionLibrary::Watchers = TMap<FString, TArray<FWatcher>>();
int TotalWatchers = 0;
UWindowsFileUtilityFunctionLibrary::UWindowsFileUtilityFunctionLibrary(const class FObjectInitializer& PCIP)
: Super(PCIP)
{
}
#if PLATFORM_WINDOWS
#include "AllowWindowsPlatformTypes.h"
#include <shellapi.h>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
bool UWindowsFileUtilityFunctionLibrary::DoesFileExist(const FString& FullPath)
{
return 0 != PathFileExistsW(*FullPath);
}
bool UWindowsFileUtilityFunctionLibrary::MoveFileTo(const FString& From, const FString& To)
{
//Using windows api
return 0 != MoveFileW(*From, *To);
}
bool UWindowsFileUtilityFunctionLibrary::CreateDirectoryAt(const FString& FullPath)
{
//Using windows api
return 0 != CreateDirectoryW(*FullPath, NULL);
}
bool UWindowsFileUtilityFunctionLibrary::DeleteFileAt(const FString& FullPath)
{
//Using windows api
return 0 != DeleteFileW(*FullPath);
}
bool UWindowsFileUtilityFunctionLibrary::DeleteEmptyFolder(const FString& FullPath)
{
//Using windows api
return 0 != RemoveDirectoryW(*FullPath);
}
bool IsSubPathOf(const FString& path, const FString& basePath)
{
return path.Contains(basePath);
}
//Dangerous function not recommended to be exposed to blueprint
bool UWindowsFileUtilityFunctionLibrary::DeleteFolderRecursively(const FString& FullPath)
{
//Only allow user to delete folders sub-class to game folder
if (!IsSubPathOf(FullPath, FPaths::ProjectDir()))
{
return false;
}
int len = _tcslen(*FullPath);
TCHAR *pszFrom = new TCHAR[len + 2];
wcscpy_s(pszFrom, len + 2, *FullPath);
pszFrom[len] = 0;
pszFrom[len + 1] = 0;
SHFILEOPSTRUCT fileop;
fileop.hwnd = NULL; // no status display
fileop.wFunc = FO_DELETE; // delete operation
fileop.pFrom = pszFrom; // source file name as double null terminated string
fileop.pTo = NULL; // no destination needed
fileop.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; // do not prompt the user
fileop.fAnyOperationsAborted = FALSE;
fileop.lpszProgressTitle = NULL;
fileop.hNameMappings = NULL;
int ret = SHFileOperation(&fileop);
delete[] pszFrom;
return (ret == 0);
}
void UWindowsFileUtilityFunctionLibrary::WatchFolder(const FString& FullPath, UObject* WatcherDelegate)
{
//Do we have an entry for this path?
if (!Watchers.Contains(FullPath))
{
//Make an entry
TArray<FWatcher> FreshList;
Watchers.Add(FullPath, FreshList);
Watchers[FullPath] = FreshList;
}
else
{
//if we do do we already watch from this object?
TArray<FWatcher>& PathWatchers = Watchers[FullPath];
for (auto Watcher : PathWatchers)
{
if (Watcher.Delegate == WatcherDelegate)
{
//Already accounted for
UE_LOG(LogTemp, Warning, TEXT("UWindowsFileUtilityFunctionLibrary::WatchFolder Duplicate watcher ignored!"));
return;
}
}
}
//Add to watchers
FWatcher FreshWatcher;
FreshWatcher.Delegate = WatcherDelegate;
FreshWatcher.Path = FullPath;
const FWatcher* WatcherPtr = &FreshWatcher;
//fork this off to another process
WFULambdaRunnable* Runnable = WFULambdaRunnable::RunLambdaOnBackGroundThread([FullPath, WatcherDelegate, WatcherPtr]()
{
UWindowsFileUtilityFunctionLibrary::WatchFolderOnBgThread(FullPath, WatcherPtr);
});
FreshWatcher.Runnable = Runnable;
TArray<FWatcher>& PathWatchers = Watchers[FullPath];
PathWatchers.Add(FreshWatcher);
}
void UWindowsFileUtilityFunctionLibrary::StopWatchingFolder(const FString& FullPath, UObject* WatcherDelegate)
{
//Do we have an entry?
if (!Watchers.Contains(FullPath))
{
return;
}
//We have an entry for this path, remove our watcher
TArray<FWatcher> PathWatchers = Watchers[FullPath];
for (int i = 0; i < PathWatchers.Num();i++)
{
FWatcher& PathWatcher = PathWatchers[i];
if (PathWatcher.Delegate == WatcherDelegate)
{
//Stop the runnable
PathWatcher.ShouldRun = false;
PathWatcher.Runnable->Stop();
//Remove the watcher and we're done
PathWatchers.RemoveAt(i);
break;
}
}
}
void UWindowsFileUtilityFunctionLibrary::ListContentsOfFolder(const FString& FullPath, UObject* Delegate)
{
//Longer than max path? throw error
if (FullPath.Len() > MAX_PATH)
{
UE_LOG(LogTemp, Warning, TEXT("UWindowsFileUtilityFunctionLibrary::ListContentsOfFolder Error, path too long, listing aborted."));
return;
}
WFULambdaRunnable* Runnable = WFULambdaRunnable::RunLambdaOnBackGroundThread([&FullPath, Delegate]()
{
WIN32_FIND_DATA ffd;
LARGE_INTEGER filesize;
HANDLE hFind = INVALID_HANDLE_VALUE;
DWORD dwError = 0;
FString SearchPath = FullPath + TEXT("\\*");
hFind = FindFirstFile(*SearchPath, &ffd);
if (INVALID_HANDLE_VALUE == hFind)
{
UE_LOG(LogTemp, Warning, TEXT("UWindowsFileUtilityFunctionLibrary::ListContentsOfFolder Error, invalid handle, listing aborted."));
return;
}
//Arrays to hold full information on Done
TArray<FString> FileNames;
TArray<FString> FolderNames;
//List loop, callback on game thread
do
{
FString Name = FString(ffd.cFileName);
FString ItemPath = FullPath + TEXT("\\") + Name;
//UE_LOG(LogTemp, Log, TEXT("Name: <%s>"), *Name);
if (Name.Equals(FString(TEXT("."))) ||
Name.Equals(FString(TEXT(".."))) )
{
//ignore these first
}
//Folder
else if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
FolderNames.Add(Name);
WFULambdaRunnable::RunShortLambdaOnGameThread([Delegate, ItemPath, Name]
{
((IWFUFileListInterface*)Delegate)->Execute_OnListDirectoryFound((UObject*)Delegate, Name, ItemPath);
});
}
//File
else
{
FileNames.Add(Name);
filesize.LowPart = ffd.nFileSizeLow;
filesize.HighPart = ffd.nFileSizeHigh;
int32 TruncatedFileSize = filesize.QuadPart;
WFULambdaRunnable::RunShortLambdaOnGameThread([Delegate, ItemPath, Name, TruncatedFileSize]
{
((IWFUFileListInterface*)Delegate)->Execute_OnListFileFound((UObject*)Delegate, Name, TruncatedFileSize, ItemPath);
});
}
} while (FindNextFile(hFind, &ffd) != 0);
dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES)
{
UE_LOG(LogTemp, Warning, TEXT("UWindowsFileUtilityFunctionLibrary::ListContentsOfFolder Error while listing."));
return;
}
FindClose(hFind);
//Done callback with full list of names found
WFULambdaRunnable::RunShortLambdaOnGameThread([Delegate, FullPath, FileNames, FolderNames]
{
((IWFUFileListInterface*)Delegate)->Execute_OnListDone((UObject*)Delegate, FullPath, FileNames, FolderNames);
});
});
}
void UWindowsFileUtilityFunctionLibrary::ListContentsOfFolderToCallback(const FString& FullPath, TFunction<void(const TArray<FString>&, const TArray<FString>&)> OnListCompleteCallback)
{
UWFUFileListLambdaDelegate* LambdaDelegate = NewObject<UWFUFileListLambdaDelegate>();
LambdaDelegate->SetOnDoneCallback(OnListCompleteCallback);
ListContentsOfFolder(FullPath, LambdaDelegate);
}
void UWindowsFileUtilityFunctionLibrary::WatchFolderOnBgThread(const FString& FullPath, const FWatcher* WatcherPtr)
{
//mostly from https://msdn.microsoft.com/en-us/library/windows/desktop/aa365261(v=vs.85).aspx
//call the delegate when the folder changes
//TODO: find out which file changed
DWORD dwWaitStatus;
HANDLE dwChangeHandles[2];
TCHAR lpDrive[4];
TCHAR lpFile[_MAX_FNAME];
TCHAR lpExt[_MAX_EXT];
//finding out about the notification
FILE_NOTIFY_INFORMATION strFileNotifyInfo[1024];
DWORD dwBytesReturned = 0;
_tsplitpath_s(*FullPath, lpDrive, 4, NULL, 0, lpFile, _MAX_FNAME, lpExt, _MAX_EXT);
lpDrive[2] = (TCHAR)'\\';
lpDrive[3] = (TCHAR)'\0';
// Watch the directory for file creation and deletion.
dwChangeHandles[0] = FindFirstChangeNotification(
*FullPath, // directory to watch
TRUE, // watch the subtree
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE// watch for generic file changes
); // watch last write or file size change
if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)
{
UE_LOG(LogTemp, Warning, TEXT("\n ERROR: FindFirstChangeNotification function failed.\n"));
//ExitProcess(GetLastError());
}
// Watch the subtree for directory creation and deletion.
dwChangeHandles[1] = FindFirstChangeNotification(
lpDrive, // directory to watch
TRUE, // watch the subtree
FILE_NOTIFY_CHANGE_DIR_NAME); // watch dir name changes
if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)
{
UE_LOG(LogTemp, Warning, TEXT("\n ERROR: FindFirstChangeNotification function failed.\n"));
//ExitProcess(GetLastError());
}
// Make a final validation check on our handles.
if ((dwChangeHandles[0] == NULL) || (dwChangeHandles[1] == NULL))
{
UE_LOG(LogTemp, Warning, TEXT("\n ERROR: Unexpected NULL from FindFirstChangeNotification.\n"));
//ExitProcess(GetLastError());
}
const FString DrivePath = FString(lpDrive);
FString FileString;
FString DirectoryString;
const UObject* WatcherDelegate = WatcherPtr->Delegate;
//Wait while the runnable pointer hasn't been set
TotalWatchers++;
UE_LOG(LogTemp, Log, TEXT("\nStarting Watcher loop %d...\n"), TotalWatchers);
while (WatcherPtr->ShouldRun) //Watcher.Runnable->Finished == false
{
// Wait for notification.
//UE_LOG(LogTemp, Log, TEXT("\nWaiting for notification...\n"));
dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles,
FALSE, INFINITE);
if (!WatcherPtr->ShouldRun)
{
UE_LOG(LogTemp, Log, TEXT("\nStop called while sleeping\n"));
break;
}
if (!WatcherDelegate->IsValidLowLevel())
{
UE_LOG(LogTemp, Warning, TEXT("\nInvalid Watcher Delegate, exiting watch\n"));
break;
}
switch (dwWaitStatus)
{
case WAIT_OBJECT_0:
ReadDirectoryChangesW(dwChangeHandles[0], (LPVOID)&strFileNotifyInfo, sizeof(strFileNotifyInfo), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, &dwBytesReturned, NULL, NULL);
//UE_LOG(LogTemp, Warning, TEXT("Received info about: %s"), strFileNotifyInfo->FileName);
FileString = FString(strFileNotifyInfo[0].FileNameLength, strFileNotifyInfo[0].FileName);
// A file was created, renamed, or deleted in the directory.
// Refresh this directory and restart the notification.
WFULambdaRunnable::RunShortLambdaOnGameThread([FullPath, FileString, WatcherDelegate]()
{
if (WatcherDelegate->GetClass()->ImplementsInterface(UWFUFolderWatchInterface::StaticClass()))
{
FString FilePath = FString::Printf(TEXT("%s\\%s"), *FullPath, *FileString);
((IWFUFolderWatchInterface*)WatcherDelegate)->Execute_OnFileChanged((UObject*)WatcherDelegate, FileString, FilePath);
}
});
if (FindNextChangeNotification(dwChangeHandles[0]) == FALSE)
{
UE_LOG(LogTemp, Warning, TEXT("\n ERROR: FindNextChangeNotification function failed.\n"));
return;
}
break;
case WAIT_OBJECT_0 + 1:
// A directory was created, renamed, or deleted.
// Refresh the tree and restart the notification.
ReadDirectoryChangesW(dwChangeHandles[1], (LPVOID)&strFileNotifyInfo, sizeof(strFileNotifyInfo), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, &dwBytesReturned, NULL, NULL);
DirectoryString = FString(strFileNotifyInfo[0].FileNameLength, strFileNotifyInfo[0].FileName);
WFULambdaRunnable::RunShortLambdaOnGameThread([FullPath, WatcherDelegate, DirectoryString]()
{
if (WatcherDelegate->GetClass()->ImplementsInterface(UWFUFolderWatchInterface::StaticClass()))
{
FString ChangedDirectoryPath = FString::Printf(TEXT("%s\\%s"), *FullPath, *DirectoryString);
((IWFUFolderWatchInterface*)WatcherDelegate)->Execute_OnDirectoryChanged((UObject*)WatcherDelegate, DirectoryString, ChangedDirectoryPath);
}
});
if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE)
{
UE_LOG(LogTemp, Warning, TEXT("\n ERROR: FindNextChangeNotification function failed.\n"));
return;
}
break;
case WAIT_TIMEOUT:
// A timeout occurred, this would happen if some value other
// than INFINITE is used in the Wait call and no changes occur.
// In a single-threaded environment you might not want an
// INFINITE wait.
UE_LOG(LogTemp, Warning, TEXT("\nNo changes in the timeout period.\n"));
break;
default:
UE_LOG(LogTemp, Warning, TEXT("\n ERROR: Unhandled dwWaitStatus.\n"));
return;
break;
}
}
TotalWatchers--;
UE_LOG(LogTemp, Log, TEXT("\n Watcher loop stopped, total now: %d.\n"), TotalWatchers);
}
#include "HideWindowsPlatformTypes.h"
#endif