|
|
- #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
|