various bugfixes and such.
also bookmarks, temp song loaderm and undo overhaul with near complete multiplayer, but menu items disabled. i really wish i could split this into multiple commits, but i don't know how to work unreal.
This commit is contained in:
squeaksies
2018-11-07 18:00:46 -08:00
parent ac6475494a
commit 7b6050b843
180 changed files with 3336 additions and 3301 deletions

View File

@@ -0,0 +1,11 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "WindowsFileUtilityPrivatePCH.h"
#include "WFUFileListInterface.h"
UWFUFileListInterface::UWFUFileListInterface(const class FObjectInitializer& PCIP)
: Super(PCIP)
{
}

View File

@@ -0,0 +1,32 @@
#include "WindowsFileUtilityPrivatePCH.h"
#include "WindowsFileUtilityFunctionLibrary.h"
#include "WFUFileListLambdaDelegate.h"
UWFUFileListLambdaDelegate::UWFUFileListLambdaDelegate()
{
OnDoneCallback = nullptr;
}
void UWFUFileListLambdaDelegate::SetOnDoneCallback(TFunction<void(const TArray<FString>&, const TArray<FString>&)> InOnDoneCallback)
{
OnDoneCallback = InOnDoneCallback;
}
void UWFUFileListLambdaDelegate::OnListFileFound_Implementation(const FString& FileName, int32 ByteCount, const FString& FilePath)
{
}
void UWFUFileListLambdaDelegate::OnListDirectoryFound_Implementation(const FString& DirectoryName, const FString& FilePath)
{
}
void UWFUFileListLambdaDelegate::OnListDone_Implementation(const FString& DirectoryPath, const TArray<FString>& Files, const TArray<FString>& Folders)
{
if (OnDoneCallback != nullptr)
{
OnDoneCallback(Files, Folders);
}
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Object.h"
#include "WFUFileListLambdaDelegate.generated.h"
UCLASS()
class WINDOWSFILEUTILITY_API UWFUFileListLambdaDelegate : public UObject, public IWFUFileListInterface
{
GENERATED_BODY()
UWFUFileListLambdaDelegate();
public:
void SetOnDoneCallback(TFunction<void(const TArray<FString>&, const TArray<FString>&)> InOnDoneCallback);
protected:
//File List Interface
virtual void OnListFileFound_Implementation(const FString& FileName, int32 ByteCount, const FString& FilePath) override;
virtual void OnListDirectoryFound_Implementation(const FString& DirectoryName, const FString& FilePath) override;
virtual void OnListDone_Implementation(const FString& DirectoryPath, const TArray<FString>& Files, const TArray<FString>& Folders) override;
TFunction<void(const TArray<FString>&, const TArray<FString>&)> OnDoneCallback;
};

View File

@@ -0,0 +1,11 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "WindowsFileUtilityPrivatePCH.h"
#include "WFUFolderWatchInterface.h"
UWFUFolderWatchInterface::UWFUFolderWatchInterface(const class FObjectInitializer& PCIP)
: Super(PCIP)
{
}

View File

@@ -0,0 +1,32 @@
#include "WindowsFileUtilityPrivatePCH.h"
#include "WindowsFileUtilityFunctionLibrary.h"
#include "WFUFolderWatchLambdaDelegate.h"
UWFUFolderWatchLambdaDelegate::UWFUFolderWatchLambdaDelegate()
{
OnFileChangedCallback = nullptr;
}
void UWFUFolderWatchLambdaDelegate::SetOnFileChangedCallback(TFunction<void(FString, FString)> InOnFileChangedCallback)
{
OnFileChangedCallback = InOnFileChangedCallback;
}
void UWFUFolderWatchLambdaDelegate::OnFileChanged_Implementation(const FString& FileName, const FString& FilePath)
{
if (OnFileChangedCallback != nullptr)
{
OnFileChangedCallback(FileName, FilePath);
}
}
void UWFUFolderWatchLambdaDelegate::OnDirectoryChanged_Implementation(const FString& DirectoryName, const FString& DirectoryPath)
{
if (OnFileChangedCallback != nullptr)
{
OnFileChangedCallback(DirectoryName, DirectoryPath);
}
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "Object.h"
#include "WFUFolderWatchLambdaDelegate.generated.h"
UCLASS()
class WINDOWSFILEUTILITY_API UWFUFolderWatchLambdaDelegate : public UObject, public IWFUFolderWatchInterface
{
GENERATED_BODY()
UWFUFolderWatchLambdaDelegate();
public:
void SetOnFileChangedCallback(TFunction<void(FString, FString)> InOnFileChangedCallback);
protected:
TFunction<void(FString, FString)> OnFileChangedCallback;
//IWFUFolderWatchInterface
virtual void OnFileChanged_Implementation(const FString& FileName, const FString& FilePath) override;
virtual void OnDirectoryChanged_Implementation(const FString& DirectoryName, const FString& DirectoryPath) override;
};

View File

@@ -0,0 +1,100 @@
#pragma once
#include "WindowsFileUtilityPrivatePCH.h"
#include "WFULambdaRunnable.h"
uint64 WFULambdaRunnable::ThreadNumber = 0;
FQueuedThreadPool* WFULambdaRunnable::ThreadPool = nullptr;
WFULambdaRunnable::WFULambdaRunnable(TFunction< void()> InFunction)
{
FunctionPointer = InFunction;
FString threadStatGroup = FString::Printf(TEXT("FLambdaRunnable%d"), ThreadNumber++);
Thread = NULL;
Thread = FRunnableThread::Create(this, *threadStatGroup, 0, TPri_BelowNormal); //windows default = 8mb for thread, could specify more
}
WFULambdaRunnable::~WFULambdaRunnable()
{
if (Thread == NULL)
{
delete Thread;
Thread = NULL;
}
ThreadPool->Destroy();
}
//Run
uint32 WFULambdaRunnable::Run()
{
if (FunctionPointer)
FunctionPointer();
//UE_LOG(LogClass, Log, TEXT("FLambdaRunnable %d Run complete"), Number);
return 0;
}
void WFULambdaRunnable::Exit()
{
//UE_LOG(LogClass, Log, TEXT("FLambdaRunnable %d Exit"), Number);
//delete ourselves when we're done
delete this;
}
void WFULambdaRunnable::InitThreadPool(int32 NumberOfThreads)
{
if (ThreadPool == nullptr)
{
ThreadPool = FQueuedThreadPool::Allocate();
int32 NumThreadsInThreadPool = NumberOfThreads;
ThreadPool->Create(NumThreadsInThreadPool, 32 * 1024);
}
}
void WFULambdaRunnable::EnsureCompletion()
{
Thread->WaitForCompletion();
}
WFULambdaRunnable* WFULambdaRunnable::RunLambdaOnBackGroundThread(TFunction< void()> InFunction)
{
if (FPlatformProcess::SupportsMultithreading())
{
//UE_LOG(LogClass, Log, TEXT("FLambdaRunnable RunLambdaBackGroundThread"));
return new WFULambdaRunnable(InFunction);
}
return nullptr;
}
IQueuedWork* WFULambdaRunnable::AddLambdaToQueue(TFunction< void()> InFunction)
{
if (ThreadPool == nullptr)
{
WFULambdaRunnable::InitThreadPool(FPlatformMisc::NumberOfIOWorkerThreadsToSpawn());
}
if (ThreadPool)
{
return AsyncLambdaPool(*ThreadPool, InFunction);
}
return nullptr;
}
bool WFULambdaRunnable::RemoveLambdaFromQueue(IQueuedWork* Work)
{
if (ThreadPool)
{
return ThreadPool->RetractQueuedWork(Work);
}
return false;
}
FGraphEventRef WFULambdaRunnable::RunShortLambdaOnGameThread(TFunction< void()> InFunction)
{
return FFunctionGraphTask::CreateAndDispatchWhenReady(InFunction, TStatId(), nullptr, ENamedThreads::GameThread);
}

View File

@@ -0,0 +1,17 @@
#include "WindowsFileUtilityPrivatePCH.h"
class FWindowsFileUtility : public IWindowsFileUtility
{
/** IModuleInterface implementation */
virtual void StartupModule() override
{
}
virtual void ShutdownModule() override
{
}
};
IMPLEMENT_MODULE(IWindowsFileUtility, WindowsFileUtility)

View File

@@ -0,0 +1,414 @@
#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

View File

@@ -0,0 +1,10 @@
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "CoreUObject.h"
#include "EngineMinimal.h"
#include "IWindowsFileUtility.h"
#include "WFULambdaRunnable.h"
#include "WFUFileListInterface.h"
#include "WFUFolderWatchInterface.h"

View File

@@ -0,0 +1,36 @@
#pragma once
#include "ModuleManager.h"
/**
* The public interface to this module. In most cases, this interface is only public to sibling modules
* within this plugin.
*/
class IWindowsFileUtility : public IModuleInterface
{
public:
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static inline IWindowsFileUtility& Get()
{
return FModuleManager::LoadModuleChecked< IWindowsFileUtility >("WindowsFileUtility");
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("WindowsFileUtility");
}
};

View File

@@ -0,0 +1,43 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "WFUFileListInterface.generated.h"
UINTERFACE(MinimalAPI)
class UWFUFileListInterface : public UInterface
{
GENERATED_UINTERFACE_BODY()
};
class WINDOWSFILEUTILITY_API IWFUFileListInterface
{
GENERATED_IINTERFACE_BODY()
public:
/**
* Called when a file has been found inside the folder of choice
* @param FileName of the found file.
* @param Size in bytes of the found file.
* @param FilePath of the file that was found
*/
UFUNCTION(BlueprintNativeEvent, Category = FolderWatchEvent)
void OnListFileFound(const FString& FileName, int32 ByteCount, const FString& FilePath);
/**
* Called when a directory has been found inside the folder of choice
* @param DirectoryName of the found directory.
* @param FilePath of the file that was found
*/
UFUNCTION(BlueprintNativeEvent, Category = FolderWatchEvent)
void OnListDirectoryFound(const FString& DirectoryName, const FString& FilePath);
/**
* Called when the listing operation has completed.
* @param DirectoryPath Path of the directory
* @param Files array of files found
*/
UFUNCTION(BlueprintNativeEvent, Category = FolderWatchEvent)
void OnListDone(const FString& DirectoryPath, const TArray<FString>& Files, const TArray<FString>& Folders);
};

View File

@@ -0,0 +1,32 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "WFUFolderWatchInterface.generated.h"
UINTERFACE(MinimalAPI)
class UWFUFolderWatchInterface : public UInterface
{
GENERATED_UINTERFACE_BODY()
};
class WINDOWSFILEUTILITY_API IWFUFolderWatchInterface
{
GENERATED_IINTERFACE_BODY()
public:
/**
* Called when a file inside the folder has changed
* @param FilePath Path of the file that has changed
*/
UFUNCTION(BlueprintNativeEvent, Category = FolderWatchEvent)
void OnFileChanged(const FString& FileName, const FString& FilePath);
/**
* Called when a directory inside the folder has changed
* @param FilePath Path of the file that has changed
*/
UFUNCTION(BlueprintNativeEvent, Category = FolderWatchEvent)
void OnDirectoryChanged(const FString& DirectoryName, const FString& DirectoryPath);
};

View File

@@ -0,0 +1,71 @@
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#pragma once
/*
Long duration lambda wrapper, which are generally not supported by the taskgraph system. New thread per lambda and they will auto-delete upon
completion.
*/
class WINDOWSFILEUTILITY_API WFULambdaRunnable : public FRunnable
{
private:
/** Thread to run the worker FRunnable on */
FRunnableThread* Thread;
// Used to give each thread a unique stat group
static uint64 ThreadNumber;
//Lambda function pointer
TFunction< void()> FunctionPointer;
// A queued threadpool used to run lambda's in the background. This has lazy initialization and is meant to be used with
// - AddLambdaToQueue
// - RemoveLAmbdaFromQueue
static FQueuedThreadPool* ThreadPool;
public:
//Constructor / Destructor
WFULambdaRunnable(TFunction< void()> InFunction);
virtual ~WFULambdaRunnable();
// Begin FRunnable interface.
virtual uint32 Run() override;
virtual void Exit() override;
// End FRunnable interface
// Initializes the queued thread pool. This is called lazily when the first task is added to the queue
// but can also be called by hand to initialize with a specific number of threads. The default number
// of threads is FPlatformMisc::NumberOfIOWorkerThreadsToSpawn() which last I checked was hard-coded
// at 4. <NOTE> that if you want to call this by hand, you need to do so before ever calling AddLambdaToQueue.
static void InitThreadPool(int32 NumberOfThreads);
/** Makes sure this thread has stopped properly */
void EnsureCompletion();
// Runs the passed lambda on the background thread, new thread per call
static WFULambdaRunnable* RunLambdaOnBackGroundThread(TFunction< void()> InFunction);
// Adds a lambda to be ran on the queued thread pool. Returns a pointer to IQueuedWork which
// can be used to later remove the queued job from the pool assuming it hasn't been processed.
static IQueuedWork* AddLambdaToQueue(TFunction< void()> InFunction);
// Removes a lambda from the thread queue
static bool RemoveLambdaFromQueue(IQueuedWork* Work);
// Runs a short lambda on the game thread via task graph system
static FGraphEventRef RunShortLambdaOnGameThread(TFunction< void()> InFunction);
private:
// This was yanked from Engine/Source/Runtime/Core/Public/Async/Async.h (originally called AsyncPool(..)). FQueuedThreadPool doesn't have
// much documentation, so using the engine code as reference, pretty much everyone seems to use this templated function to queue up work.
// It was modified to return an IQueuedWork instead of a TFuture to be more convenient for actually removing items from the queue.
template<typename ResultType>
static IQueuedWork* AsyncLambdaPool(FQueuedThreadPool& ThreadPool, TFunction<ResultType()> Function, TFunction<void()> CompletionCallback = TFunction<void()>())
{
TPromise<ResultType> Promise(MoveTemp(CompletionCallback));
TFuture<ResultType> Future = Promise.GetFuture();
IQueuedWork* Work = new TAsyncQueuedWork<ResultType>(MoveTemp(Function), MoveTemp(Promise));
ThreadPool.AddQueuedWork(Work);
return Work;
}
};

View File

@@ -0,0 +1,74 @@
#pragma once
#include "WFULambdaRunnable.h"
#include "WindowsFileUtilityFunctionLibrary.generated.h"
//Struct to Track which delegate is watching files
struct FWatcher
{
UObject* Delegate;
FString Path;
WFULambdaRunnable* Runnable = nullptr;
FThreadSafeBool ShouldRun = true;
};
inline bool operator==(const FWatcher& lhs, const FWatcher& rhs)
{
return lhs.Delegate == rhs.Delegate;
}
UCLASS(ClassGroup = WindowsFileUtility, Blueprintable)
class WINDOWSFILEUTILITY_API UWindowsFileUtilityFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_UCLASS_BODY()
UFUNCTION(BlueprintCallable, Category = WindowsFileUtility)
static bool DoesFileExist(const FString& FullPath);
/*Expects full path including name. you can use this function to rename files.*/
UFUNCTION(BlueprintCallable, Category = WindowsFileUtility)
static bool MoveFileTo(const FString& From, const FString& To);
/*Expects full path including folder name.*/
UFUNCTION(BlueprintCallable, Category = WindowsFileUtility)
static bool CreateDirectoryAt(const FString& FullPath);
/*Deletes file (not directory). Expects full path.*/
UFUNCTION(BlueprintCallable, Category = WindowsFileUtility)
static bool DeleteFileAt(const FString& FullPath);
/*Deletes empty folders only. Expects full path.*/
UFUNCTION(BlueprintCallable, Category = WindowsFileUtility)
static bool DeleteEmptyFolder(const FString& FullPath);
/*Dangerous function, not exposed to blueprint. */
UFUNCTION(BlueprintCallable, Category = WindowsFileUtility)
static bool DeleteFolderRecursively(const FString& FullPath);
/** Watch a folder for change. WatcherDelegate should respond to FolderWatchInterface*/
UFUNCTION(BlueprintCallable, Category = WindowsFileUtility)
static void WatchFolder(const FString& FullPath, UObject* WatcherDelegate);
/** Stop watching a folder for change. WatcherDelegate should respond to FolderWatchInterface*/
UFUNCTION(BlueprintCallable, Category = WindowsFileUtility)
static void StopWatchingFolder(const FString& FullPath, UObject* WatcherDelegate);
/** List the contents, expects UFileListInterface*/
UFUNCTION(BlueprintCallable, Category = WindowsFileUtility)
static void ListContentsOfFolder(const FString& FullPath, UObject* ListDelegate);
//Convenience C++ callback
static void ListContentsOfFolderToCallback(const FString& FullPath, TFunction<void(const TArray<FString>&, const TArray<FString>&)> OnListCompleteCallback);
//Todo: add watch folder with threadsafe boolean passthrough
//static void ListContentsOfFolderToCallback(const FString& FullPath, TFunction<void(const TArray<FString>&, const TArray<FString>&)> OnListCompleteCallback);
private:
static void WatchFolderOnBgThread(const FString& FullPath, const FWatcher* Watcher);
static TMap<FString, TArray<FWatcher>> Watchers;
};

View File

@@ -0,0 +1,51 @@
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.IO;
public class WindowsFileUtility : ModuleRules
{
public WindowsFileUtility(ReadOnlyTargetRules Target) : base(Target)
{
PublicIncludePaths.AddRange(
new string[] {
"WindowsFileUtility/Public"
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
"WindowsFileUtility/Private",
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@@ -0,0 +1,100 @@
#include "ZipUtilityPrivatePCH.h"
#include "ZipFileFunctionLibrary.h"
#include "SevenZipCallbackHandler.h"
void SevenZipCallbackHandler::OnProgress(const TString& archivePath, uint64 bytes)
{
const UObject* interfaceDelegate = ProgressDelegate;
const uint64 bytesConst = bytes;
const FString pathConst = FString(archivePath.c_str());
if (bytes > 0) {
const float ProgressPercentage = ((double)((TotalBytes)-(BytesLeft - bytes)) / (double)TotalBytes) * 100;
UZipFileFunctionLibrary::RunLambdaOnGameThread([interfaceDelegate, pathConst, ProgressPercentage, bytesConst]
{
//UE_LOG(LogClass, Log, TEXT("Progress: %d bytes"), progress);
((IZipUtilityInterface*)interfaceDelegate)->Execute_OnProgress((UObject*)interfaceDelegate, pathConst, ProgressPercentage, bytesConst);
});
}
}
void SevenZipCallbackHandler::OnDone(const TString& archivePath)
{
const UObject* interfaceDelegate = ProgressDelegate;
const FString pathConst = FString(archivePath.c_str());
UZipFileFunctionLibrary::RunLambdaOnGameThread([pathConst, interfaceDelegate]
{
//UE_LOG(LogClass, Log, TEXT("All Done!"));
((IZipUtilityInterface*)interfaceDelegate)->Execute_OnDone((UObject*)interfaceDelegate, pathConst, EZipUtilityCompletionState::SUCCESS);
});
}
void SevenZipCallbackHandler::OnFileDone(const TString& archivePath, const TString& filePath, uint64 bytes)
{
const UObject* interfaceDelegate = ProgressDelegate;
const FString pathConst = FString(archivePath.c_str());
const FString filePathConst = FString(filePath.c_str());
const uint64 bytesConst = bytes;
UZipFileFunctionLibrary::RunLambdaOnGameThread([interfaceDelegate, pathConst, filePathConst, bytesConst]
{
//UE_LOG(LogClass, Log, TEXT("File Done: %s, %d bytes"), filePathConst.c_str(), bytesConst);
((IZipUtilityInterface*)interfaceDelegate)->Execute_OnFileDone((UObject*)interfaceDelegate, pathConst, filePathConst);
});
//Handle byte decrementing
if (bytes > 0) {
BytesLeft -= bytes;
const float ProgressPercentage = ((double)(TotalBytes - BytesLeft) / (double)TotalBytes) * 100;
UZipFileFunctionLibrary::RunLambdaOnGameThread([interfaceDelegate, pathConst, ProgressPercentage, bytes]
{
//UE_LOG(LogClass, Log, TEXT("Progress: %d bytes"), progress);
((IZipUtilityInterface*)interfaceDelegate)->Execute_OnProgress((UObject*)interfaceDelegate, pathConst, ProgressPercentage, bytes);
});
}
}
void SevenZipCallbackHandler::OnStartWithTotal(const TString& archivePath, unsigned __int64 totalBytes)
{
TotalBytes = totalBytes;
BytesLeft = TotalBytes;
const UObject* interfaceDelegate = ProgressDelegate;
const uint64 bytesConst = TotalBytes;
const FString pathConst = FString(archivePath.c_str());
UZipFileFunctionLibrary::RunLambdaOnGameThread([interfaceDelegate, pathConst, bytesConst]
{
//UE_LOG(LogClass, Log, TEXT("Starting with %d bytes"), bytesConst);
((IZipUtilityInterface*)interfaceDelegate)->Execute_OnStartProcess((UObject*)interfaceDelegate, pathConst, bytesConst);
});
}
void SevenZipCallbackHandler::OnFileFound(const TString& archivePath, const TString& filePath, int size)
{
const UObject* interfaceDelegate = ProgressDelegate;
const uint64 bytesConst = TotalBytes;
const FString pathString = FString(archivePath.c_str());
const FString fileString = FString(filePath.c_str());
UZipFileFunctionLibrary::RunLambdaOnGameThread([interfaceDelegate, pathString, fileString, bytesConst]
{
((IZipUtilityInterface*)interfaceDelegate)->Execute_OnFileFound((UObject*)interfaceDelegate, pathString, fileString, bytesConst);
});
}
void SevenZipCallbackHandler::OnListingDone(const TString& archivePath)
{
const UObject* interfaceDelegate = ProgressDelegate;
const FString pathString = FString(archivePath.c_str());
UZipFileFunctionLibrary::RunLambdaOnGameThread([interfaceDelegate, pathString]
{
((IZipUtilityInterface*)interfaceDelegate)->Execute_OnDone((UObject*)interfaceDelegate, pathString, EZipUtilityCompletionState::SUCCESS);
});
}
bool SevenZipCallbackHandler::OnCheckBreak()
{
return bCancelOperation;
}

View File

@@ -0,0 +1,53 @@
#include "ZipUtilityPrivatePCH.h"
#include "WindowsFileUtilityFunctionLibrary.h"
#include "ZipFileFunctionLibrary.h"
#include "ZULambdaDelegate.h"
UZULambdaDelegate::UZULambdaDelegate()
{
OnDoneCallback = nullptr;
OnProgressCallback = nullptr;
}
void UZULambdaDelegate::SetOnDoneCallback(TFunction<void()> InOnDoneCallback)
{
OnDoneCallback = InOnDoneCallback;
}
void UZULambdaDelegate::SetOnProgessCallback(TFunction<void(float)> InOnProgressCallback)
{
OnProgressCallback = InOnProgressCallback;
}
void UZULambdaDelegate::OnProgress_Implementation(const FString& archive, float percentage, int32 bytes)
{
if (OnProgressCallback != nullptr)
{
OnProgressCallback(percentage);
}
}
void UZULambdaDelegate::OnDone_Implementation(const FString& archive, EZipUtilityCompletionState CompletionState)
{
if (OnDoneCallback != nullptr)
{
OnDoneCallback();
}
}
void UZULambdaDelegate::OnStartProcess_Implementation(const FString& archive, int32 bytes)
{
}
void UZULambdaDelegate::OnFileDone_Implementation(const FString& archive, const FString& file)
{
}
void UZULambdaDelegate::OnFileFound_Implementation(const FString& archive, const FString& file, int32 size)
{
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include "Object.h"
#include "ZULambdaDelegate.generated.h"
UCLASS()
class ZIPUTILITY_API UZULambdaDelegate : public UObject, public IZipUtilityInterface
{
GENERATED_BODY()
UZULambdaDelegate();
public:
void SetOnDoneCallback(TFunction<void()> InOnDoneCallback);
void SetOnProgessCallback(TFunction<void(float)> InOnProgressCallback);
protected:
//Zip utility interface
virtual void OnProgress_Implementation(const FString& archive, float percentage, int32 bytes) override;
virtual void OnDone_Implementation(const FString& archive, EZipUtilityCompletionState CompletionState) override;
virtual void OnStartProcess_Implementation(const FString& archive, int32 bytes) override;
virtual void OnFileDone_Implementation(const FString& archive, const FString& file) override;
virtual void OnFileFound_Implementation(const FString& archive, const FString& file, int32 size) override;
TFunction<void()> OnDoneCallback;
TFunction<void(float)> OnProgressCallback;
};

View File

@@ -0,0 +1,51 @@
#include "ZipUtilityPrivatePCH.h"
#include "ZipFileFunctionInternalCallback.h"
UZipFileFunctionInternalCallback::UZipFileFunctionInternalCallback()
{
CompressionFormat = ZipUtilityCompressionFormat::COMPRESSION_FORMAT_UNKNOWN;
DestinationFolder = FString();
File = FString();
FileIndex = 0;
Callback = NULL;
}
void UZipFileFunctionInternalCallback::OnFileFound_Implementation(const FString& archive, const FString& fileIn, int32 size)
{
if (!bFileFound && fileIn.ToLower().Contains(File.ToLower()))
{
TArray<int32> FileIndices = { FileIndex };
if (bUnzipto)
{
UZipFileFunctionLibrary::UnzipFilesTo(FileIndices, archive, DestinationFolder, Callback, CompressionFormat);
}
else
{
UZipFileFunctionLibrary::UnzipFiles(FileIndices, archive, Callback, CompressionFormat);
}
if (bSingleFile)
{
bFileFound = true;
}
}
FileIndex++;
}
void UZipFileFunctionInternalCallback::SetCallback(const FString& FileName, UObject* CallbackIn, TEnumAsByte<ZipUtilityCompressionFormat> CompressionFormatIn /*= ZipUtilityCompressionFormat::COMPRESSION_FORMAT_UNKNOWN*/)
{
File = FileName;
Callback = CallbackIn;
CompressionFormat = CompressionFormatIn;
FileIndex = 0;
}
void UZipFileFunctionInternalCallback::SetCallback(const FString& FileName, const FString& DestinationFolderIn, UObject* CallbackIn, TEnumAsByte<ZipUtilityCompressionFormat> CompressionFormatIn /*= ZipUtilityCompressionFormat::COMPRESSION_FORMAT_UNKNOWN*/)
{
SetCallback(FileName, CallbackIn, CompressionFormatIn);
bUnzipto = true;
DestinationFolder = DestinationFolderIn;
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include "ZipUtilityInterface.h"
#include "ZipFileFunctionInternalCallback.generated.h"
/**
* This is used to provide a callback for unzipping single files, it ends up getting called from the ListFiles lambda.
*/
UCLASS(ClassGroup = ZipUtility)
class ZIPUTILITY_API UZipFileFunctionInternalCallback : public UObject, public IZipUtilityInterface
{
GENERATED_BODY()
private:
/** Compression format used to unzip */
UPROPERTY(Transient)
TEnumAsByte<ZipUtilityCompressionFormat> CompressionFormat;
/** Path of the file */
UPROPERTY(Transient)
FString File;
UPROPERTY(Transient)
FString DestinationFolder;
/** Current File index parsed */
UPROPERTY(Transient)
int32 FileIndex = 0;
/** Callback object */
UPROPERTY(Transient)
UObject* Callback;
UPROPERTY(Transient)
bool bSingleFile;
UPROPERTY(Transient)
bool bFileFound;
UPROPERTY(Transient)
bool bUnzipto;
public:
UZipFileFunctionInternalCallback();
//IZipUtilityInterface overrides
virtual void OnProgress_Implementation(const FString& archive, float percentage, int32 bytes) override {};
virtual void OnDone_Implementation(const FString& archive, EZipUtilityCompletionState CompletionState) override {};
virtual void OnStartProcess_Implementation(const FString& archive, int32 bytes) override {};
virtual void OnFileDone_Implementation(const FString& archive, const FString& file) override {
UE_LOG(LogTemp, Log, TEXT("OnFileDone_Implementation"));
};
virtual void OnFileFound_Implementation(const FString& archive, const FString& fileIn, int32 size) override;
void SetCallback(const FString& FileName, UObject* CallbackIn, TEnumAsByte<ZipUtilityCompressionFormat> CompressionFormatIn = ZipUtilityCompressionFormat::COMPRESSION_FORMAT_UNKNOWN);
void SetCallback(const FString& FileName, const FString& DestinationFolder, UObject* CallbackIn, TEnumAsByte<ZipUtilityCompressionFormat> CompressionFormatIn = ZipUtilityCompressionFormat::COMPRESSION_FORMAT_UNKNOWN);
FORCEINLINE bool GetSingleFile() const { return bSingleFile; }
FORCEINLINE void SetSingleFile(bool val) { bSingleFile = val; }
};

View File

@@ -0,0 +1,481 @@
#include "ZipUtilityPrivatePCH.h"
#include "ZipFileFunctionLibrary.h"
#include "ZipFileFunctionInternalCallback.h"
#include "ListCallback.h"
#include "ProgressCallback.h"
#include "IPluginManager.h"
#include "WFULambdaRunnable.h"
#include "ZULambdaDelegate.h"
#include "SevenZipCallbackHandler.h"
#include "WindowsFileUtilityFunctionLibrary.h"
#include "7zpp.h"
using namespace SevenZip;
//Private Namespace
namespace{
//Threaded Lambda convenience wrappers - Task graph is only suitable for short duration lambdas, but doesn't incur thread overhead
FGraphEventRef RunLambdaOnAnyThread(TFunction< void()> InFunction)
{
return FFunctionGraphTask::CreateAndDispatchWhenReady(InFunction, TStatId(), nullptr, ENamedThreads::AnyThread);
}
//Uses proper threading, for any task that may run longer than about 2 seconds.
void RunLongLambdaOnAnyThread(TFunction< void()> InFunction)
{
WFULambdaRunnable::RunLambdaOnBackGroundThread(InFunction);
}
// Run the lambda on the queued threadpool
IQueuedWork* RunLambdaOnThreadPool(TFunction< void()> InFunction)
{
return WFULambdaRunnable::AddLambdaToQueue(InFunction);
}
//Private static vars
SevenZipLibrary SZLib;
//Utility functions
FString PluginRootFolder()
{
return IPluginManager::Get().FindPlugin("ZipUtility")->GetBaseDir();
//return FPaths::ConvertRelativePathToFull(FPaths::GameDir());
}
FString DLLPath()
{
#if _WIN64
FString PlatformString = FString(TEXT("Win64"));
#else
FString PlatformString = FString(TEXT("Win32"));
#endif
//Swap these to change which license you wish to fall under for zip-utility
FString DLLString = FString("7z.dll"); //Using 7z.dll: GNU LGPL + unRAR restriction
//FString dllString = FString("7za.dll"); //Using 7za.dll: GNU LGPL license, crucially doesn't support .zip out of the box
return FPaths::ConvertRelativePathToFull(FPaths::Combine(*PluginRootFolder(), TEXT("ThirdParty/7zpp/dll"), *PlatformString, *DLLString));
}
FString ReversePathSlashes(FString forwardPath)
{
return forwardPath.Replace(TEXT("/"), TEXT("\\"));
}
bool IsValidDirectory(FString& Directory, FString& FileName, const FString& Path)
{
bool Found = Path.Split(TEXT("/"), &Directory, &FileName, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
//try a back split
if (!Found)
{
Found = Path.Split(TEXT("\\"), &Directory, &FileName, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
}
//No valid Directory found
if (!Found)
return false;
else
return true;
}
SevenZip::CompressionLevelEnum libZipLevelFromUELevel(ZipUtilityCompressionLevel ueLevel) {
switch (ueLevel)
{
case COMPRESSION_LEVEL_NONE:
return SevenZip::CompressionLevel::None;
case COMPRESSION_LEVEL_FAST:
return SevenZip::CompressionLevel::Fast;
case COMPRESSION_LEVEL_NORMAL:
return SevenZip::CompressionLevel::Normal;
default:
return SevenZip::CompressionLevel::None;
}
}
SevenZip::CompressionFormatEnum libZipFormatFromUEFormat(ZipUtilityCompressionFormat UeFormat) {
switch (UeFormat)
{
case COMPRESSION_FORMAT_UNKNOWN:
return CompressionFormat::Unknown;
case COMPRESSION_FORMAT_SEVEN_ZIP:
return CompressionFormat::SevenZip;
case COMPRESSION_FORMAT_ZIP:
return CompressionFormat::Zip;
case COMPRESSION_FORMAT_GZIP:
return CompressionFormat::GZip;
case COMPRESSION_FORMAT_BZIP2:
return CompressionFormat::BZip2;
case COMPRESSION_FORMAT_RAR:
return CompressionFormat::Rar;
case COMPRESSION_FORMAT_TAR:
return CompressionFormat::Tar;
case COMPRESSION_FORMAT_ISO:
return CompressionFormat::Iso;
case COMPRESSION_FORMAT_CAB:
return CompressionFormat::Cab;
case COMPRESSION_FORMAT_LZMA:
return CompressionFormat::Lzma;
case COMPRESSION_FORMAT_LZMA86:
return CompressionFormat::Lzma86;
default:
return CompressionFormat::Unknown;
}
}
FString defaultExtensionFromUEFormat(ZipUtilityCompressionFormat ueFormat)
{
switch (ueFormat)
{
case COMPRESSION_FORMAT_UNKNOWN:
return FString(TEXT(".dat"));
case COMPRESSION_FORMAT_SEVEN_ZIP:
return FString(TEXT(".7z"));
case COMPRESSION_FORMAT_ZIP:
return FString(TEXT(".zip"));
case COMPRESSION_FORMAT_GZIP:
return FString(TEXT(".gz"));
case COMPRESSION_FORMAT_BZIP2:
return FString(TEXT(".bz2"));
case COMPRESSION_FORMAT_RAR:
return FString(TEXT(".rar"));
case COMPRESSION_FORMAT_TAR:
return FString(TEXT(".tar"));
case COMPRESSION_FORMAT_ISO:
return FString(TEXT(".iso"));
case COMPRESSION_FORMAT_CAB:
return FString(TEXT(".cab"));
case COMPRESSION_FORMAT_LZMA:
return FString(TEXT(".lzma"));
case COMPRESSION_FORMAT_LZMA86:
return FString(TEXT(".lzma86"));
default:
return FString(TEXT(".dat"));
}
}
using namespace std;
//Background Thread convenience functions
UZipOperation* UnzipFilesOnBGThreadWithFormat(const TArray<int32> FileIndices, const FString& ArchivePath, const FString& DestinationDirectory, const UObject* ProgressDelegate, ZipUtilityCompressionFormat Format)
{
UZipOperation* ZipOperation = NewObject<UZipOperation>();
IQueuedWork* Work = RunLambdaOnThreadPool([ProgressDelegate, FileIndices, ArchivePath, DestinationDirectory, Format, ZipOperation]
{
SevenZipCallbackHandler PrivateCallback;
PrivateCallback.ProgressDelegate = (UObject*)ProgressDelegate;
ZipOperation->SetCallbackHandler(&PrivateCallback);
//UE_LOG(LogClass, Log, TEXT("path is: %s"), *path);
SevenZipExtractor Extractor(SZLib, *ArchivePath);
if (Format == COMPRESSION_FORMAT_UNKNOWN)
{
if (!Extractor.DetectCompressionFormat())
{
UE_LOG(LogTemp, Log, TEXT("auto-compression detection did not succeed, passing in unknown format to 7zip library."));
}
}
else
{
Extractor.SetCompressionFormat(libZipFormatFromUEFormat(Format));
}
// Extract indices
const int32 NumberFiles = FileIndices.Num();
unsigned int* Indices = new unsigned int[NumberFiles];
for (int32 idx = 0; idx < NumberFiles; idx++)
{
Indices[idx] = FileIndices[idx];
}
// Perform the extraction
Extractor.ExtractFilesFromArchive(Indices, NumberFiles, *DestinationDirectory, &PrivateCallback);
// Clean up the indices
delete Indices;
// Null out the callback handler now that we're exiting
ZipOperation->SetCallbackHandler(nullptr);
});
ZipOperation->SetThreadPoolWorker(Work);
return ZipOperation;
}
//Background Thread convenience functions
UZipOperation* UnzipOnBGThreadWithFormat(const FString& ArchivePath, const FString& DestinationDirectory, const UObject* ProgressDelegate, ZipUtilityCompressionFormat Format)
{
UZipOperation* ZipOperation = NewObject<UZipOperation>();
IQueuedWork* Work = RunLambdaOnThreadPool([ProgressDelegate, ArchivePath, DestinationDirectory, Format, ZipOperation]
{
SevenZipCallbackHandler PrivateCallback;
PrivateCallback.ProgressDelegate = (UObject*)ProgressDelegate;
ZipOperation->SetCallbackHandler(&PrivateCallback);
//UE_LOG(LogClass, Log, TEXT("path is: %s"), *path);
SevenZipExtractor Extractor(SZLib, *ArchivePath);
if (Format == COMPRESSION_FORMAT_UNKNOWN)
{
if (!Extractor.DetectCompressionFormat())
{
UE_LOG(LogTemp, Log, TEXT("auto-compression detection did not succeed, passing in unknown format to 7zip library."));
}
}
else
{
Extractor.SetCompressionFormat(libZipFormatFromUEFormat(Format));
}
Extractor.ExtractArchive(*DestinationDirectory, &PrivateCallback);
// Null out the callback handler now that we're exiting
ZipOperation->SetCallbackHandler(nullptr);
});
ZipOperation->SetThreadPoolWorker(Work);
return ZipOperation;
}
void ListOnBGThread(const FString& Path, const FString& Directory, const UObject* ListDelegate, ZipUtilityCompressionFormat Format)
{
//RunLongLambdaOnAnyThread - this shouldn't take long, but if it lags, swap the lambda methods
RunLambdaOnAnyThread([ListDelegate, Path, Format, Directory] {
SevenZipCallbackHandler PrivateCallback;
PrivateCallback.ProgressDelegate = (UObject*)ListDelegate;
SevenZipLister Lister(SZLib, *Path);
if (Format == COMPRESSION_FORMAT_UNKNOWN)
{
if (!Lister.DetectCompressionFormat())
{
UE_LOG(LogTemp, Log, TEXT("auto-compression detection did not succeed, passing in unknown format to 7zip library."));
}
}
else
{
Lister.SetCompressionFormat(libZipFormatFromUEFormat(Format));
}
if (!Lister.ListArchive(&PrivateCallback))
{
// If ListArchive returned false, it was most likely because the compression format was unsupported
// Call OnDone with a failure message, make sure to call this on the game thread.
if (IZipUtilityInterface* ZipInterface = Cast<IZipUtilityInterface>((UObject*)ListDelegate))
{
UE_LOG(LogClass, Warning, TEXT("ZipUtility: Unknown failure for list operation on %s"), *Path);
UZipFileFunctionLibrary::RunLambdaOnGameThread([ZipInterface, ListDelegate, Path]
{
ZipInterface->Execute_OnDone((UObject*)ListDelegate, *Path, EZipUtilityCompletionState::FAILURE_UNKNOWN);
});
}
}
});
}
UZipOperation* ZipOnBGThread(const FString& Path, const FString& FileName, const FString& Directory, const UObject* ProgressDelegate, ZipUtilityCompressionFormat UeCompressionformat, ZipUtilityCompressionLevel UeCompressionlevel)
{
UZipOperation* ZipOperation = NewObject<UZipOperation>();
IQueuedWork* Work = RunLambdaOnThreadPool([ProgressDelegate, FileName, Path, UeCompressionformat, UeCompressionlevel, Directory, ZipOperation]
{
SevenZipCallbackHandler PrivateCallback;
PrivateCallback.ProgressDelegate = (UObject*)ProgressDelegate;
ZipOperation->SetCallbackHandler(&PrivateCallback);
//Set the zip format
ZipUtilityCompressionFormat UeFormat = UeCompressionformat;
if (UeFormat == COMPRESSION_FORMAT_UNKNOWN)
{
UeFormat = COMPRESSION_FORMAT_ZIP;
}
//Disallow creating .rar archives as per unrar restriction, this won't work anyway so redirect to 7z
else if (UeFormat == COMPRESSION_FORMAT_RAR)
{
UE_LOG(LogClass, Warning, TEXT("ZipUtility: Rar compression not supported for creating archives, re-targeting as 7z."));
UeFormat = COMPRESSION_FORMAT_SEVEN_ZIP;
}
//concatenate the output filename
FString OutputFileName = FString::Printf(TEXT("%s/%s%s"), *Directory, *FileName, *defaultExtensionFromUEFormat(UeFormat));
//UE_LOG(LogClass, Log, TEXT("\noutputfile is: <%s>\n path is: <%s>"), *outputFileName, *path);
SevenZipCompressor compressor(SZLib, *ReversePathSlashes(OutputFileName));
compressor.SetCompressionFormat(libZipFormatFromUEFormat(UeFormat));
compressor.SetCompressionLevel(libZipLevelFromUELevel(UeCompressionlevel));
if (PathIsDirectory(*Path))
{
//UE_LOG(LogClass, Log, TEXT("Compressing Folder"));
compressor.CompressDirectory(*ReversePathSlashes(Path), &PrivateCallback);
}
else
{
//UE_LOG(LogClass, Log, TEXT("Compressing File"));
compressor.CompressFile(*ReversePathSlashes(Path), &PrivateCallback);
}
// Null out the callback handler
ZipOperation->SetCallbackHandler(nullptr);
//Todo: expand to support zipping up contents of current folder
//compressor.CompressFiles(*ReversePathSlashes(path), TEXT("*"), &PrivateCallback);
});
ZipOperation->SetThreadPoolWorker(Work);
return ZipOperation;
}
}//End private namespace
UZipFileFunctionLibrary::UZipFileFunctionLibrary(const class FObjectInitializer& PCIP)
: Super(PCIP)
{
UE_LOG(LogTemp, Log, TEXT("DLLPath is: %s"), *DLLPath());
SZLib.Load(*DLLPath());
}
UZipFileFunctionLibrary::~UZipFileFunctionLibrary()
{
SZLib.Free();
}
bool UZipFileFunctionLibrary::UnzipFileNamed(const FString& archivePath, const FString& Name, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format /*= COMPRESSION_FORMAT_UNKNOWN*/)
{
UZipFileFunctionInternalCallback* InternalCallback = NewObject<UZipFileFunctionInternalCallback>();
InternalCallback->SetFlags(RF_MarkAsRootSet);
InternalCallback->SetCallback(Name, ZipUtilityInterfaceDelegate, format);
ListFilesInArchive(archivePath, InternalCallback, format);
return true;
}
bool UZipFileFunctionLibrary::UnzipFileNamedTo(const FString& archivePath, const FString& Name, const FString& destinationPath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format /*= COMPRESSION_FORMAT_UNKNOWN*/)
{
UZipFileFunctionInternalCallback* InternalCallback = NewObject<UZipFileFunctionInternalCallback>();
InternalCallback->SetFlags(RF_MarkAsRootSet);
InternalCallback->SetCallback(Name, destinationPath, ZipUtilityInterfaceDelegate, format);
ListFilesInArchive(archivePath, InternalCallback, format);
return true;
}
UZipOperation* UZipFileFunctionLibrary::UnzipFilesTo(const TArray<int32> fileIndices, const FString & archivePath, const FString & destinationPath, UObject * ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format)
{
return UnzipFilesOnBGThreadWithFormat(fileIndices, archivePath, destinationPath, ZipUtilityInterfaceDelegate, format);
}
UZipOperation* UZipFileFunctionLibrary::UnzipFiles(const TArray<int32> fileIndices, const FString & ArchivePath, UObject * ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format)
{
FString Directory;
FString FileName;
//Check Directory validity
if (!IsValidDirectory(Directory, FileName, ArchivePath))
{
return nullptr;
}
if (fileIndices.Num() == 0)
{
return nullptr;
}
return UnzipFilesTo(fileIndices, ArchivePath, Directory, ZipUtilityInterfaceDelegate, format);
}
UZipOperation* UZipFileFunctionLibrary::Unzip(const FString& ArchivePath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat Format /*= COMPRESSION_FORMAT_UNKNOWN*/)
{
FString Directory;
FString FileName;
//Check Directory validity
if (!IsValidDirectory(Directory, FileName, ArchivePath) || !UWindowsFileUtilityFunctionLibrary::DoesFileExist(ArchivePath))
{
((IZipUtilityInterface*)ZipUtilityInterfaceDelegate)->Execute_OnDone((UObject*)ZipUtilityInterfaceDelegate, ArchivePath, EZipUtilityCompletionState::FAILURE_NOT_FOUND);
return nullptr;
}
return UnzipTo(ArchivePath, Directory, ZipUtilityInterfaceDelegate, Format);
}
UZipOperation* UZipFileFunctionLibrary::UnzipWithLambda(const FString& ArchivePath, TFunction<void()> OnDoneCallback, TFunction<void(float)> OnProgressCallback, ZipUtilityCompressionFormat Format)
{
UZULambdaDelegate* LambdaDelegate = NewObject<UZULambdaDelegate>();
LambdaDelegate->AddToRoot();
LambdaDelegate->SetOnDoneCallback([LambdaDelegate, OnDoneCallback]()
{
OnDoneCallback();
LambdaDelegate->RemoveFromRoot();
});
LambdaDelegate->SetOnProgessCallback(OnProgressCallback);
return Unzip(ArchivePath, LambdaDelegate, Format);
}
UZipOperation* UZipFileFunctionLibrary::UnzipTo(const FString& ArchivePath, const FString& DestinationPath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat Format)
{
return UnzipOnBGThreadWithFormat(ArchivePath, DestinationPath, ZipUtilityInterfaceDelegate, Format);
}
UZipOperation* UZipFileFunctionLibrary::Zip(const FString& ArchivePath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat Format, TEnumAsByte<ZipUtilityCompressionLevel> Level)
{
FString Directory;
FString FileName;
//Check Directory and File validity
if (!IsValidDirectory(Directory, FileName, ArchivePath) || !UWindowsFileUtilityFunctionLibrary::DoesFileExist(ArchivePath))
{
((IZipUtilityInterface*)ZipUtilityInterfaceDelegate)->Execute_OnDone((UObject*)ZipUtilityInterfaceDelegate, ArchivePath, EZipUtilityCompletionState::FAILURE_NOT_FOUND);
return nullptr;
}
return ZipOnBGThread(ArchivePath, FileName, Directory, ZipUtilityInterfaceDelegate, Format, Level);
}
UZipOperation* UZipFileFunctionLibrary::ZipWithLambda(const FString& ArchivePath, TFunction<void()> OnDoneCallback, TFunction<void(float)> OnProgressCallback /*= nullptr*/, ZipUtilityCompressionFormat Format /*= COMPRESSION_FORMAT_UNKNOWN*/, TEnumAsByte<ZipUtilityCompressionLevel> Level /*=COMPRESSION_LEVEL_NORMAL*/)
{
UZULambdaDelegate* LambdaDelegate = NewObject<UZULambdaDelegate>();
LambdaDelegate->AddToRoot();
LambdaDelegate->SetOnDoneCallback([OnDoneCallback, LambdaDelegate]()
{
OnDoneCallback();
LambdaDelegate->RemoveFromRoot();
});
LambdaDelegate->SetOnProgessCallback(OnProgressCallback);
return Zip(ArchivePath, LambdaDelegate, Format);
}
bool UZipFileFunctionLibrary::ListFilesInArchive(const FString& path, UObject* ListDelegate, ZipUtilityCompressionFormat format)
{
FString Directory;
FString FileName;
//Check Directory validity
if (!IsValidDirectory(Directory, FileName, path))
{
return false;
}
ListOnBGThread(path, Directory, ListDelegate, format);
return true;
}
FGraphEventRef UZipFileFunctionLibrary::RunLambdaOnGameThread(TFunction< void()> InFunction)
{
return FFunctionGraphTask::CreateAndDispatchWhenReady(InFunction, TStatId(), nullptr, ENamedThreads::GameThread);
}

View File

@@ -0,0 +1,34 @@
#include "ZipUtilityPrivatePCH.h"
#include "SevenZipCallbackHandler.h"
#include "WFULambdaRunnable.h"
#include "ZipOperation.h"
UZipOperation::UZipOperation()
{
CallbackHandler = nullptr;
}
void UZipOperation::StopOperation()
{
if (ThreadPoolWork != nullptr)
{
WFULambdaRunnable::RemoveLambdaFromQueue(ThreadPoolWork);
}
if (CallbackHandler != nullptr)
{
CallbackHandler->bCancelOperation = true;
CallbackHandler = nullptr;
}
}
void UZipOperation::SetCallbackHandler(SevenZipCallbackHandler* Handler)
{
CallbackHandler = Handler;
}
void UZipOperation::SetThreadPoolWorker(IQueuedWork* Work)
{
ThreadPoolWork = Work;
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include "ZipUtilityPrivatePCH.h"
#include "ZipUtilityInterface.h"
UZipUtilityInterface::UZipUtilityInterface(const class FObjectInitializer& PCIP)
: Super(PCIP)
{
}

View File

@@ -0,0 +1,20 @@
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "ZipUtilityPrivatePCH.h"
#define LOCTEXT_NAMESPACE "FZipUtilityModule"
void FZipUtilityModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FZipUtilityModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FZipUtilityModule, ZipUtility)

View File

@@ -0,0 +1,12 @@
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "ZipUtilityPlugin.h"
#include "CoreMinimal.h"
#include "CoreUObject.h"
#include "EngineMinimal.h"
// You should place include statements to your module's private header files here. You only need to
// add includes for headers that are used in most of your module's source files though.
#include "ZipFileFunctionLibrary.h"

View File

@@ -0,0 +1,28 @@
#pragma once
#include "CoreMinimal.h"
#include "7zpp.h"
#include "ListCallback.h"
#include "ProgressCallback.h"
using namespace SevenZip;
/**
* Forwards events from the 7zpp library to the UE4 listener.
*/
class ZIPUTILITY_API SevenZipCallbackHandler : public ListCallback, public ProgressCallback
{
public:
virtual void OnProgress(const TString& archivePath, uint64 bytes) override;
virtual void OnDone(const TString& archivePath) override;
virtual void OnFileDone(const TString& archivePath, const TString& filePath, uint64 bytes) override;
virtual void OnStartWithTotal(const TString& archivePath, unsigned __int64 totalBytes) override;
virtual void OnFileFound(const TString& archivePath, const TString& filePath, int size) override;
virtual void OnListingDone(const TString& archivePath) override;
virtual bool OnCheckBreak() override;
uint64 BytesLeft = 0;
uint64 TotalBytes = 0;
UObject* ProgressDelegate = nullptr;
FThreadSafeBool bCancelOperation = false;
};

View File

@@ -0,0 +1,102 @@
#pragma once
#include "ZipUtilityInterface.h"
#include "ZipOperation.h"
#include "ZipFileFunctionLibrary.generated.h"
UENUM(BlueprintType)
enum ZipUtilityCompressionFormat
{
COMPRESSION_FORMAT_UNKNOWN,
COMPRESSION_FORMAT_SEVEN_ZIP,
COMPRESSION_FORMAT_ZIP,
COMPRESSION_FORMAT_GZIP,
COMPRESSION_FORMAT_BZIP2,
COMPRESSION_FORMAT_RAR,
COMPRESSION_FORMAT_TAR,
COMPRESSION_FORMAT_ISO,
COMPRESSION_FORMAT_CAB,
COMPRESSION_FORMAT_LZMA,
COMPRESSION_FORMAT_LZMA86
};
UENUM(BlueprintType)
enum ZipUtilityCompressionLevel
{
COMPRESSION_LEVEL_NONE,
COMPRESSION_LEVEL_FAST,
COMPRESSION_LEVEL_NORMAL
};
class SevenZipCallbackHandler;
class UZipFileFunctionInternalCallback;
/**
A blueprint function library encapsulating all zip operations for both C++ and blueprint use.
For some operations a UZipOperation object may be returned, if you're interested in it, ensure
you guard it from garbage collection by e.g. storing it as a UProperty, otherwise you may safely
ignore it.
*/
UCLASS(ClassGroup = ZipUtility, Blueprintable)
class ZIPUTILITY_API UZipFileFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_UCLASS_BODY()
public:
~UZipFileFunctionLibrary();
/* Unzips file in archive containing Name via ListFilesInArchive/UnzipFiles. Automatically determines compression if unknown. Calls ZipUtilityInterface progress events. */
UFUNCTION(BlueprintCallable, Category = ZipUtility)
static bool UnzipFileNamed(const FString& archivePath, const FString& Name, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format = COMPRESSION_FORMAT_UNKNOWN);
/* Unzips file in archive containing Name at destination path via ListFilesInArchive/UnzipFilesTo. Automatically determines compression if unknown. Calls ZipUtilityInterface progress events. */
UFUNCTION(BlueprintCallable, Category = ZipUtility)
static bool UnzipFileNamedTo(const FString& archivePath, const FString& Name, const FString& destinationPath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format = COMPRESSION_FORMAT_UNKNOWN);
/* Unzips the given file indexes in archive at destination path. Automatically determines compression if unknown. Calls ZipUtilityInterface progress events. */
UFUNCTION(BlueprintCallable, Category = ZipUtility)
static UZipOperation* UnzipFilesTo(const TArray<int32> fileIndices, const FString& archivePath, const FString& destinationPath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format = COMPRESSION_FORMAT_UNKNOWN);
/* Unzips the given file indexes in archive at current path. Automatically determines compression if unknown. Calls ZipUtilityInterface progress events. */
UFUNCTION(BlueprintCallable, Category = ZipUtility)
static UZipOperation* UnzipFiles(const TArray<int32> fileIndices, const FString& ArchivePath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format = COMPRESSION_FORMAT_UNKNOWN);
/* Unzips archive at current path. Automatically determines compression if unknown. Calls ZipUtilityInterface progress events. */
UFUNCTION(BlueprintCallable, Category = ZipUtility)
static UZipOperation* Unzip(const FString& ArchivePath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat Format = COMPRESSION_FORMAT_UNKNOWN);
/* Lambda C++ simple variant*/
static UZipOperation* UnzipWithLambda( const FString& ArchivePath,
TFunction<void()> OnDoneCallback,
TFunction<void(float)> OnProgressCallback = nullptr,
ZipUtilityCompressionFormat format = COMPRESSION_FORMAT_UNKNOWN);
/* Unzips archive at destination path. Automatically determines compression if unknown. Calls ZipUtilityInterface progress events. */
UFUNCTION(BlueprintCallable, Category = ZipUtility)
static UZipOperation* UnzipTo(const FString& ArchivePath, const FString& DestinationPath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format = COMPRESSION_FORMAT_UNKNOWN);
/* Compresses the file or folder given at path and places the file in the same root folder. Calls ZipUtilityInterface progress events. Not all formats are supported for compression.*/
UFUNCTION(BlueprintCallable, Category = ZipUtility)
static UZipOperation* Zip( const FString& FileOrFolderPath,
UObject* ZipUtilityInterfaceDelegate,
ZipUtilityCompressionFormat Format = COMPRESSION_FORMAT_SEVEN_ZIP,
TEnumAsByte<ZipUtilityCompressionLevel> Level = COMPRESSION_LEVEL_NORMAL);
/* Lambda C++ simple variant*/
static UZipOperation* ZipWithLambda( const FString& ArchivePath,
TFunction<void()> OnDoneCallback,
TFunction<void(float)> OnProgressCallback = nullptr,
ZipUtilityCompressionFormat Format = COMPRESSION_FORMAT_UNKNOWN,
TEnumAsByte<ZipUtilityCompressionLevel> Level = COMPRESSION_LEVEL_NORMAL);
/*Queries Archive content list, calls ZipUtilityInterface list events (OnFileFound)*/
UFUNCTION(BlueprintCallable, Category = ZipUtility)
static bool ListFilesInArchive(const FString& ArchivePath, UObject* ZipUtilityInterfaceDelegate, ZipUtilityCompressionFormat format = COMPRESSION_FORMAT_UNKNOWN);
static FGraphEventRef RunLambdaOnGameThread(TFunction< void()> InFunction);
};

View File

@@ -0,0 +1,42 @@
#pragma once
#include "Object.h"
#include "ZipOperation.generated.h"
class SevenZipCallbackHandler;
/**
* Used to track a zip/unzip operation on the WFULambdaRunnable ThreadPool and allows the ability to terminate the
* operation early.
*/
UCLASS(BlueprintType)
class ZIPUTILITY_API UZipOperation : public UObject
{
GENERATED_BODY()
public:
UZipOperation();
// Stops this zip/unzip if it is still valid. This stop event can occur in two places:
// 1. The ThreadPool queue, if it can be stopped here then the operation has not yet started.
// 2. The SevenZip layer, once the operation has started, it can be canceled while still running.
// Note that calling this carries no guarantees of a successful stop, the end result might be one of:
// * The file still got extracted (you were too late)
// * The file was never extracted (caught it on time)
// * The file was created but is of zero size (oops)
// * The file was created, is of non-zero size but is not all there (cut off in the middle)
// So, it could be a good idea to do some file housekeeping afterward.
UFUNCTION(BlueprintCallable, Category = "Zip Operation")
void StopOperation();
// Set the callback handler
void SetCallbackHandler(SevenZipCallbackHandler* Handler);
// Set the queued work
void SetThreadPoolWorker(IQueuedWork* Work);
private:
// A pointer to the callback for this operation. Once the operation completes, this
// pointer will become invalid.
SevenZipCallbackHandler* CallbackHandler;
// The work that was queued on the async threadpool in WFULambdaRunnable
IQueuedWork* ThreadPoolWork;
};

View File

@@ -0,0 +1,62 @@
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ZipUtilityInterface.generated.h"
UENUM(BlueprintType)
enum EZipUtilityCompletionState
{
SUCCESS,
FAILURE_NOT_FOUND,
FAILURE_UNKNOWN
};
UINTERFACE(MinimalAPI)
class UZipUtilityInterface : public UInterface
{
GENERATED_UINTERFACE_BODY()
};
class ZIPUTILITY_API IZipUtilityInterface
{
GENERATED_IINTERFACE_BODY()
public:
/**
* Called during process as it completes. Currently updates on per file progress.
* @param percentage - percentage done
*/
UFUNCTION(BlueprintNativeEvent, Category = ZipUtilityProgressEvents)
void OnProgress(const FString& archive, float percentage, int32 bytes);
/**
* Called when whole process is complete (e.g. unzipping completed on archive)
*/
UFUNCTION(BlueprintNativeEvent, Category = ZipUtilityProgressEvents)
void OnDone(const FString& archive, EZipUtilityCompletionState CompletionState);
/**
* Called at beginning of process (NB this only supports providing size information for up to 2gb) TODO: fix 32bit BP size issue
*/
UFUNCTION(BlueprintNativeEvent, Category = ZipUtilityProgressEvents)
void OnStartProcess(const FString& archive, int32 bytes);
/**
* Called when file process is complete
* @param path - path of the file that finished
*/
UFUNCTION(BlueprintNativeEvent, Category = ZipUtilityProgressEvents)
void OnFileDone(const FString& archive, const FString& file);
/**
* Called when a file is found in the archive (e.g. listing the entries in the archive)
* @param path - path of file
* @param size - compressed size
*/
UFUNCTION(BlueprintNativeEvent, Category = ZipUtilityListEvents)
void OnFileFound(const FString& archive, const FString& file, int32 size);
};

View File

@@ -0,0 +1,38 @@
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ModuleManager.h"
class ZIPUTILITY_API FZipUtilityModule : public IModuleInterface
{
public:
//CHN:
//Maybe we need to use a public interface to fetch the module methods for C++? get a reference to zipfile etc
/**
* Singleton-like access to this module's interface. This is just for convenience!
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
*
* @return Returns singleton instance, loading the module on demand if needed
*/
static inline FZipUtilityModule& Get()
{
return FModuleManager::LoadModuleChecked< FZipUtilityModule >("ZipUtility");
}
/**
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
*
* @return True if the module is loaded and ready to use
*/
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("ZipUtility");
}
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@@ -0,0 +1,102 @@
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.IO;
public class ZipUtility : ModuleRules
{
private string ThirdPartyPath
{
get { return Path.GetFullPath(Path.Combine(ModuleDirectory, "../../ThirdParty/")); }
}
private string SevenZppPath
{
get { return Path.GetFullPath(Path.Combine(ThirdPartyPath, "7zpp")); }
}
private string ATLPath
{
get { return "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.13.26128/atlmfc"; }
}
public ZipUtility(ReadOnlyTargetRules Target) : base(Target)
{
bEnableExceptions = true;
PublicIncludePaths.AddRange(
new string[] {
"ZipUtility/Public"
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
"ZipUtility/Private",
Path.Combine(SevenZppPath, "Include"),
Path.Combine(ATLPath, "include"),
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"WindowsFileUtility"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"Projects"
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
LoadLib(Target);
}
public bool LoadLib(ReadOnlyTargetRules Target)
{
bool isLibrarySupported = false;
if ((Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Win32))
{
isLibrarySupported = true;
string PlatformSubPath = (Target.Platform == UnrealTargetPlatform.Win64) ? "Win64" : "Win32";
string LibrariesPath = Path.Combine(SevenZppPath, "Lib");
string DLLPath = Path.Combine(SevenZppPath, "dll");
PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, PlatformSubPath, "atls.lib"));
PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, PlatformSubPath, "7zpp_u.lib"));
PublicLibraryPaths.Add(Path.Combine(LibrariesPath, PlatformSubPath));
PublicDelayLoadDLLs.Add("7z.dll");
RuntimeDependencies.Add(new RuntimeDependency(Path.Combine(DLLPath, PlatformSubPath, "7z.dll")));
}
if (isLibrarySupported)
{
// Include path
//PublicIncludePaths.Add(Path.Combine(SevenZppPath, "Include"));
}
return isLibrarySupported;
}
}