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.

413 lines
12 KiB

  1. #include "WindowsFileUtilityPrivatePCH.h"
  2. #include "WFUFolderWatchInterface.h"
  3. #include "WFUFileListInterface.h"
  4. #include "WindowsFileUtilityFunctionLibrary.h"
  5. //static TMAP definition
  6. TMap<FString, TArray<FWatcher>> UWindowsFileUtilityFunctionLibrary::Watchers = TMap<FString, TArray<FWatcher>>();
  7. int TotalWatchers = 0;
  8. UWindowsFileUtilityFunctionLibrary::UWindowsFileUtilityFunctionLibrary(const class FObjectInitializer& PCIP)
  9. : Super(PCIP)
  10. {
  11. }
  12. #if PLATFORM_WINDOWS
  13. #include "AllowWindowsPlatformTypes.h"
  14. #include <shellapi.h>
  15. #include <Shlwapi.h>
  16. #pragma comment(lib, "Shlwapi.lib")
  17. bool UWindowsFileUtilityFunctionLibrary::DoesFileExist(const FString& FullPath)
  18. {
  19. return 0 != PathFileExistsW(*FullPath);
  20. }
  21. bool UWindowsFileUtilityFunctionLibrary::MoveFileTo(const FString& From, const FString& To)
  22. {
  23. //Using windows api
  24. return 0 != MoveFileW(*From, *To);
  25. }
  26. bool UWindowsFileUtilityFunctionLibrary::CreateDirectoryAt(const FString& FullPath)
  27. {
  28. //Using windows api
  29. return 0 != CreateDirectoryW(*FullPath, NULL);
  30. }
  31. bool UWindowsFileUtilityFunctionLibrary::DeleteFileAt(const FString& FullPath)
  32. {
  33. //Using windows api
  34. return 0 != DeleteFileW(*FullPath);
  35. }
  36. bool UWindowsFileUtilityFunctionLibrary::DeleteEmptyFolder(const FString& FullPath)
  37. {
  38. //Using windows api
  39. return 0 != RemoveDirectoryW(*FullPath);
  40. }
  41. bool IsSubPathOf(const FString& path, const FString& basePath)
  42. {
  43. return path.Contains(basePath);
  44. }
  45. //Dangerous function not recommended to be exposed to blueprint
  46. bool UWindowsFileUtilityFunctionLibrary::DeleteFolderRecursively(const FString& FullPath)
  47. {
  48. //Only allow user to delete folders sub-class to game folder
  49. if (!IsSubPathOf(FullPath, FPaths::ProjectDir()))
  50. {
  51. return false;
  52. }
  53. int len = _tcslen(*FullPath);
  54. TCHAR *pszFrom = new TCHAR[len + 2];
  55. wcscpy_s(pszFrom, len + 2, *FullPath);
  56. pszFrom[len] = 0;
  57. pszFrom[len + 1] = 0;
  58. SHFILEOPSTRUCT fileop;
  59. fileop.hwnd = NULL; // no status display
  60. fileop.wFunc = FO_DELETE; // delete operation
  61. fileop.pFrom = pszFrom; // source file name as double null terminated string
  62. fileop.pTo = NULL; // no destination needed
  63. fileop.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; // do not prompt the user
  64. fileop.fAnyOperationsAborted = FALSE;
  65. fileop.lpszProgressTitle = NULL;
  66. fileop.hNameMappings = NULL;
  67. int ret = SHFileOperation(&fileop);
  68. delete[] pszFrom;
  69. return (ret == 0);
  70. }
  71. void UWindowsFileUtilityFunctionLibrary::WatchFolder(const FString& FullPath, UObject* WatcherDelegate)
  72. {
  73. //Do we have an entry for this path?
  74. if (!Watchers.Contains(FullPath))
  75. {
  76. //Make an entry
  77. TArray<FWatcher> FreshList;
  78. Watchers.Add(FullPath, FreshList);
  79. Watchers[FullPath] = FreshList;
  80. }
  81. else
  82. {
  83. //if we do do we already watch from this object?
  84. TArray<FWatcher>& PathWatchers = Watchers[FullPath];
  85. for (auto Watcher : PathWatchers)
  86. {
  87. if (Watcher.Delegate == WatcherDelegate)
  88. {
  89. //Already accounted for
  90. UE_LOG(LogTemp, Warning, TEXT("UWindowsFileUtilityFunctionLibrary::WatchFolder Duplicate watcher ignored!"));
  91. return;
  92. }
  93. }
  94. }
  95. //Add to watchers
  96. FWatcher FreshWatcher;
  97. FreshWatcher.Delegate = WatcherDelegate;
  98. FreshWatcher.Path = FullPath;
  99. const FWatcher* WatcherPtr = &FreshWatcher;
  100. //fork this off to another process
  101. WFULambdaRunnable* Runnable = WFULambdaRunnable::RunLambdaOnBackGroundThread([FullPath, WatcherDelegate, WatcherPtr]()
  102. {
  103. UWindowsFileUtilityFunctionLibrary::WatchFolderOnBgThread(FullPath, WatcherPtr);
  104. });
  105. FreshWatcher.Runnable = Runnable;
  106. TArray<FWatcher>& PathWatchers = Watchers[FullPath];
  107. PathWatchers.Add(FreshWatcher);
  108. }
  109. void UWindowsFileUtilityFunctionLibrary::StopWatchingFolder(const FString& FullPath, UObject* WatcherDelegate)
  110. {
  111. //Do we have an entry?
  112. if (!Watchers.Contains(FullPath))
  113. {
  114. return;
  115. }
  116. //We have an entry for this path, remove our watcher
  117. TArray<FWatcher> PathWatchers = Watchers[FullPath];
  118. for (int i = 0; i < PathWatchers.Num();i++)
  119. {
  120. FWatcher& PathWatcher = PathWatchers[i];
  121. if (PathWatcher.Delegate == WatcherDelegate)
  122. {
  123. //Stop the runnable
  124. PathWatcher.ShouldRun = false;
  125. PathWatcher.Runnable->Stop();
  126. //Remove the watcher and we're done
  127. PathWatchers.RemoveAt(i);
  128. break;
  129. }
  130. }
  131. }
  132. void UWindowsFileUtilityFunctionLibrary::ListContentsOfFolder(const FString& FullPath, UObject* Delegate)
  133. {
  134. //Longer than max path? throw error
  135. if (FullPath.Len() > MAX_PATH)
  136. {
  137. UE_LOG(LogTemp, Warning, TEXT("UWindowsFileUtilityFunctionLibrary::ListContentsOfFolder Error, path too long, listing aborted."));
  138. return;
  139. }
  140. WFULambdaRunnable* Runnable = WFULambdaRunnable::RunLambdaOnBackGroundThread([&FullPath, Delegate]()
  141. {
  142. WIN32_FIND_DATA ffd;
  143. LARGE_INTEGER filesize;
  144. HANDLE hFind = INVALID_HANDLE_VALUE;
  145. DWORD dwError = 0;
  146. FString SearchPath = FullPath + TEXT("\\*");
  147. hFind = FindFirstFile(*SearchPath, &ffd);
  148. if (INVALID_HANDLE_VALUE == hFind)
  149. {
  150. UE_LOG(LogTemp, Warning, TEXT("UWindowsFileUtilityFunctionLibrary::ListContentsOfFolder Error, invalid handle, listing aborted."));
  151. return;
  152. }
  153. //Arrays to hold full information on Done
  154. TArray<FString> FileNames;
  155. TArray<FString> FolderNames;
  156. //List loop, callback on game thread
  157. do
  158. {
  159. FString Name = FString(ffd.cFileName);
  160. FString ItemPath = FullPath + TEXT("\\") + Name;
  161. //UE_LOG(LogTemp, Log, TEXT("Name: <%s>"), *Name);
  162. if (Name.Equals(FString(TEXT("."))) ||
  163. Name.Equals(FString(TEXT(".."))) )
  164. {
  165. //ignore these first
  166. }
  167. //Folder
  168. else if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  169. {
  170. FolderNames.Add(Name);
  171. WFULambdaRunnable::RunShortLambdaOnGameThread([Delegate, ItemPath, Name]
  172. {
  173. ((IWFUFileListInterface*)Delegate)->Execute_OnListDirectoryFound((UObject*)Delegate, Name, ItemPath);
  174. });
  175. }
  176. //File
  177. else
  178. {
  179. FileNames.Add(Name);
  180. filesize.LowPart = ffd.nFileSizeLow;
  181. filesize.HighPart = ffd.nFileSizeHigh;
  182. int32 TruncatedFileSize = filesize.QuadPart;
  183. WFULambdaRunnable::RunShortLambdaOnGameThread([Delegate, ItemPath, Name, TruncatedFileSize]
  184. {
  185. ((IWFUFileListInterface*)Delegate)->Execute_OnListFileFound((UObject*)Delegate, Name, TruncatedFileSize, ItemPath);
  186. });
  187. }
  188. } while (FindNextFile(hFind, &ffd) != 0);
  189. dwError = GetLastError();
  190. if (dwError != ERROR_NO_MORE_FILES)
  191. {
  192. UE_LOG(LogTemp, Warning, TEXT("UWindowsFileUtilityFunctionLibrary::ListContentsOfFolder Error while listing."));
  193. return;
  194. }
  195. FindClose(hFind);
  196. //Done callback with full list of names found
  197. WFULambdaRunnable::RunShortLambdaOnGameThread([Delegate, FullPath, FileNames, FolderNames]
  198. {
  199. ((IWFUFileListInterface*)Delegate)->Execute_OnListDone((UObject*)Delegate, FullPath, FileNames, FolderNames);
  200. });
  201. });
  202. }
  203. void UWindowsFileUtilityFunctionLibrary::ListContentsOfFolderToCallback(const FString& FullPath, TFunction<void(const TArray<FString>&, const TArray<FString>&)> OnListCompleteCallback)
  204. {
  205. UWFUFileListLambdaDelegate* LambdaDelegate = NewObject<UWFUFileListLambdaDelegate>();
  206. LambdaDelegate->SetOnDoneCallback(OnListCompleteCallback);
  207. ListContentsOfFolder(FullPath, LambdaDelegate);
  208. }
  209. void UWindowsFileUtilityFunctionLibrary::WatchFolderOnBgThread(const FString& FullPath, const FWatcher* WatcherPtr)
  210. {
  211. //mostly from https://msdn.microsoft.com/en-us/library/windows/desktop/aa365261(v=vs.85).aspx
  212. //call the delegate when the folder changes
  213. //TODO: find out which file changed
  214. DWORD dwWaitStatus;
  215. HANDLE dwChangeHandles[2];
  216. TCHAR lpDrive[4];
  217. TCHAR lpFile[_MAX_FNAME];
  218. TCHAR lpExt[_MAX_EXT];
  219. //finding out about the notification
  220. FILE_NOTIFY_INFORMATION strFileNotifyInfo[1024];
  221. DWORD dwBytesReturned = 0;
  222. _tsplitpath_s(*FullPath, lpDrive, 4, NULL, 0, lpFile, _MAX_FNAME, lpExt, _MAX_EXT);
  223. lpDrive[2] = (TCHAR)'\\';
  224. lpDrive[3] = (TCHAR)'\0';
  225. // Watch the directory for file creation and deletion.
  226. dwChangeHandles[0] = FindFirstChangeNotification(
  227. *FullPath, // directory to watch
  228. TRUE, // watch the subtree
  229. FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE |
  230. FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE// watch for generic file changes
  231. ); // watch last write or file size change
  232. if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)
  233. {
  234. UE_LOG(LogTemp, Warning, TEXT("\n ERROR: FindFirstChangeNotification function failed.\n"));
  235. //ExitProcess(GetLastError());
  236. }
  237. // Watch the subtree for directory creation and deletion.
  238. dwChangeHandles[1] = FindFirstChangeNotification(
  239. lpDrive, // directory to watch
  240. TRUE, // watch the subtree
  241. FILE_NOTIFY_CHANGE_DIR_NAME); // watch dir name changes
  242. if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)
  243. {
  244. UE_LOG(LogTemp, Warning, TEXT("\n ERROR: FindFirstChangeNotification function failed.\n"));
  245. //ExitProcess(GetLastError());
  246. }
  247. // Make a final validation check on our handles.
  248. if ((dwChangeHandles[0] == NULL) || (dwChangeHandles[1] == NULL))
  249. {
  250. UE_LOG(LogTemp, Warning, TEXT("\n ERROR: Unexpected NULL from FindFirstChangeNotification.\n"));
  251. //ExitProcess(GetLastError());
  252. }
  253. const FString DrivePath = FString(lpDrive);
  254. FString FileString;
  255. FString DirectoryString;
  256. const UObject* WatcherDelegate = WatcherPtr->Delegate;
  257. //Wait while the runnable pointer hasn't been set
  258. TotalWatchers++;
  259. UE_LOG(LogTemp, Log, TEXT("\nStarting Watcher loop %d...\n"), TotalWatchers);
  260. while (WatcherPtr->ShouldRun) //Watcher.Runnable->Finished == false
  261. {
  262. // Wait for notification.
  263. //UE_LOG(LogTemp, Log, TEXT("\nWaiting for notification...\n"));
  264. dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles,
  265. FALSE, INFINITE);
  266. if (!WatcherPtr->ShouldRun)
  267. {
  268. UE_LOG(LogTemp, Log, TEXT("\nStop called while sleeping\n"));
  269. break;
  270. }
  271. if (!WatcherDelegate->IsValidLowLevel())
  272. {
  273. UE_LOG(LogTemp, Warning, TEXT("\nInvalid Watcher Delegate, exiting watch\n"));
  274. break;
  275. }
  276. switch (dwWaitStatus)
  277. {
  278. case WAIT_OBJECT_0:
  279. ReadDirectoryChangesW(dwChangeHandles[0], (LPVOID)&strFileNotifyInfo, sizeof(strFileNotifyInfo), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, &dwBytesReturned, NULL, NULL);
  280. //UE_LOG(LogTemp, Warning, TEXT("Received info about: %s"), strFileNotifyInfo->FileName);
  281. FileString = FString(strFileNotifyInfo[0].FileNameLength, strFileNotifyInfo[0].FileName);
  282. // A file was created, renamed, or deleted in the directory.
  283. // Refresh this directory and restart the notification.
  284. WFULambdaRunnable::RunShortLambdaOnGameThread([FullPath, FileString, WatcherDelegate]()
  285. {
  286. if (WatcherDelegate->GetClass()->ImplementsInterface(UWFUFolderWatchInterface::StaticClass()))
  287. {
  288. FString FilePath = FString::Printf(TEXT("%s\\%s"), *FullPath, *FileString);
  289. ((IWFUFolderWatchInterface*)WatcherDelegate)->Execute_OnFileChanged((UObject*)WatcherDelegate, FileString, FilePath);
  290. }
  291. });
  292. if (FindNextChangeNotification(dwChangeHandles[0]) == FALSE)
  293. {
  294. UE_LOG(LogTemp, Warning, TEXT("\n ERROR: FindNextChangeNotification function failed.\n"));
  295. return;
  296. }
  297. break;
  298. case WAIT_OBJECT_0 + 1:
  299. // A directory was created, renamed, or deleted.
  300. // Refresh the tree and restart the notification.
  301. ReadDirectoryChangesW(dwChangeHandles[1], (LPVOID)&strFileNotifyInfo, sizeof(strFileNotifyInfo), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, &dwBytesReturned, NULL, NULL);
  302. DirectoryString = FString(strFileNotifyInfo[0].FileNameLength, strFileNotifyInfo[0].FileName);
  303. WFULambdaRunnable::RunShortLambdaOnGameThread([FullPath, WatcherDelegate, DirectoryString]()
  304. {
  305. if (WatcherDelegate->GetClass()->ImplementsInterface(UWFUFolderWatchInterface::StaticClass()))
  306. {
  307. FString ChangedDirectoryPath = FString::Printf(TEXT("%s\\%s"), *FullPath, *DirectoryString);
  308. ((IWFUFolderWatchInterface*)WatcherDelegate)->Execute_OnDirectoryChanged((UObject*)WatcherDelegate, DirectoryString, ChangedDirectoryPath);
  309. }
  310. });
  311. if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE)
  312. {
  313. UE_LOG(LogTemp, Warning, TEXT("\n ERROR: FindNextChangeNotification function failed.\n"));
  314. return;
  315. }
  316. break;
  317. case WAIT_TIMEOUT:
  318. // A timeout occurred, this would happen if some value other
  319. // than INFINITE is used in the Wait call and no changes occur.
  320. // In a single-threaded environment you might not want an
  321. // INFINITE wait.
  322. UE_LOG(LogTemp, Warning, TEXT("\nNo changes in the timeout period.\n"));
  323. break;
  324. default:
  325. UE_LOG(LogTemp, Warning, TEXT("\n ERROR: Unhandled dwWaitStatus.\n"));
  326. return;
  327. break;
  328. }
  329. }
  330. TotalWatchers--;
  331. UE_LOG(LogTemp, Log, TEXT("\n Watcher loop stopped, total now: %d.\n"), TotalWatchers);
  332. }
  333. #include "HideWindowsPlatformTypes.h"
  334. #endif