Skip to content

Commit 19366c9

Browse files
committed
add async functions for copying files/dirs
1 parent 16002e5 commit 19366c9

File tree

3 files changed

+313
-12
lines changed

3 files changed

+313
-12
lines changed

FileSDK.uplugin

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"FileVersion": 3,
3-
"Version": 12,
4-
"VersionName": "1.12",
3+
"Version": 13,
4+
"VersionName": "1.13",
55
"FriendlyName": "Blueprint File SDK",
66
"Description": "A simple plugin to interact with files and folders within blueprints.",
77
"Category": "Code Plugins",

Source/FileSDK/Private/FileSDKBPLibrary.cpp

Lines changed: 243 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,21 +83,260 @@ bool UFileSDKBPLibrary::RenameFileOrDirectory(
8383

8484
bool UFileSDKBPLibrary::CopyFile(
8585
FString Source,
86-
FString Destination
86+
FString Destination,
87+
const FFileSDKCopyDelegate & ProgressCallback,
88+
FFileSDKDelegatePreInfo PreInfo,
89+
int ChunkSizeInKilobytes
8790
) {
8891
IPlatformFile & PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
8992

90-
return PlatformFile.CopyFile(*Destination, *Source);
93+
const int64 MaxBufferSize = ChunkSizeInKilobytes * 1024;
94+
95+
// Note, the below code is mostly copied from the
96+
// engine's GenericPlatformFile.cpp source file
97+
98+
TUniquePtr<IFileHandle> FromFile(PlatformFile.OpenRead(*Source, false));
99+
if (!FromFile) {
100+
return false;
101+
}
102+
103+
TUniquePtr<IFileHandle> ToFile(PlatformFile.OpenWrite(*Destination, false, false));
104+
if (!ToFile) {
105+
return false;
106+
}
107+
108+
int64 Size = FromFile->Size();
109+
int totalSizeKb = FMath::DivideAndRoundUp(Size, int64(1000));
110+
if (Size < 1) {
111+
check(Size == 0);
112+
return true;
113+
}
114+
115+
int64 AllocSize = FMath::Min<int64>(MaxBufferSize, Size);
116+
check(AllocSize);
117+
118+
uint8* Buffer = (uint8*)FMemory::Malloc(int32(AllocSize));
119+
check(Buffer);
120+
121+
while (Size) {
122+
int64 ThisSize = FMath::Min<int64>(AllocSize, Size);
123+
FromFile->Read(Buffer, ThisSize);
124+
ToFile->Write(Buffer, ThisSize);
125+
Size -= ThisSize;
126+
ProgressCallback.ExecuteIfBound(
127+
PreInfo.PriorWritten + totalSizeKb - FMath::DivideAndRoundUp(Size, int64(1000)),
128+
PreInfo.TotalSize > 0 ? PreInfo.TotalSize : totalSizeKb
129+
);
130+
check(Size >= 0);
131+
}
132+
133+
FMemory::Free(Buffer);
134+
135+
#if PLATFORM_MAC || PLATFORM_IOS
136+
struct stat FileInfo;
137+
auto applePlatformFile = static_cast<FApplePlatformFile>(PlatformFile);
138+
if (applePlatformFile.Stat(*Source, &FileInfo) == 0) {
139+
FileInfo.st_mode |= S_IWUSR;
140+
chmod(TCHAR_TO_UTF8(*applePlatformFile.NormalizeFilename(*Destination)), FileInfo.st_mode);
141+
}
142+
#endif
143+
144+
return true;
145+
}
146+
147+
void UFileSDKBPLibrary::CopyFileAsync(
148+
FString Source,
149+
FString Destination,
150+
const FFileSDKCopyDelegate & ProgressCallback,
151+
FFileSDKDelegatePreInfo PreInfo,
152+
int ChunkSizeInKilobytes
153+
) {
154+
FFunctionGraphTask::CreateAndDispatchWhenReady(
155+
[=] {
156+
UFileSDKBPLibrary::CopyFile(
157+
Source,
158+
Destination,
159+
ProgressCallback,
160+
PreInfo,
161+
ChunkSizeInKilobytes
162+
);
163+
},
164+
TStatId(),
165+
nullptr,
166+
ENamedThreads::AnyThread
167+
);
91168
}
92169

93170
bool UFileSDKBPLibrary::CopyDirectory(
94171
FString Source,
95172
FString Destination,
96-
bool OverwriteDestination
173+
const FFileSDKCopyDelegate & ProgressCallback,
174+
bool OverwriteDestination,
175+
int ChunkSizeInKilobytes
97176
) {
98177
IPlatformFile & PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
99178

100-
return PlatformFile.CopyDirectoryTree(*Destination, *Source, OverwriteDestination);
179+
// Note, the below code is mostly copied from the
180+
// engine's GenericPlatformFile.cpp source file
181+
182+
check(*Destination);
183+
check(*Source);
184+
185+
FString DestDir(Destination);
186+
FPaths::NormalizeDirectoryName(DestDir);
187+
188+
FString SourceDir(Source);
189+
FPaths::NormalizeDirectoryName(SourceDir);
190+
191+
// Does Source dir exist?
192+
if (!PlatformFile.DirectoryExists(*SourceDir)) {
193+
return false;
194+
}
195+
196+
// Destination directory exists already or can be created ?
197+
if (
198+
!PlatformFile.DirectoryExists(*DestDir) &&
199+
!PlatformFile.CreateDirectory(*DestDir)
200+
) {
201+
return false;
202+
}
203+
204+
// Get total size
205+
struct FStatFilesAndDirs : public IPlatformFile::FDirectoryStatVisitor {
206+
IPlatformFile & PlatformFile;
207+
int64 TotalFileSize;
208+
209+
FStatFilesAndDirs(IPlatformFile& InPlatformFile)
210+
: PlatformFile(InPlatformFile)
211+
, TotalFileSize(0) {
212+
}
213+
214+
virtual bool Visit(const TCHAR* FilenameOrDirectory, const FFileStatData& StatData) {
215+
if (!StatData.bIsDirectory) {
216+
TotalFileSize += StatData.FileSize;
217+
}
218+
219+
return true;
220+
}
221+
};
222+
223+
// copy files and directories visitor
224+
FStatFilesAndDirs StatFilesAndDirs(PlatformFile);
225+
226+
// don't bother getting file size of dir
227+
// if the user doesn't want it
228+
if (ProgressCallback.IsBound()) {
229+
bool statResult = PlatformFile.IterateDirectoryStatRecursively(*SourceDir, StatFilesAndDirs);
230+
231+
if (!statResult) {
232+
return false;
233+
}
234+
}
235+
236+
// Copy all files and directories
237+
struct FCopyFilesAndDirs : public IPlatformFile::FDirectoryVisitor {
238+
IPlatformFile & PlatformFile;
239+
const TCHAR* SourceRoot;
240+
const TCHAR* DestRoot;
241+
bool bOverwrite;
242+
int chunkSizeInKilobytes;
243+
FFileSDKCopyDelegate progressCallback;
244+
FFileSDKDelegatePreInfo preInfo;
245+
246+
FCopyFilesAndDirs(
247+
IPlatformFile& InPlatformFile,
248+
const TCHAR* InSourceRoot,
249+
const TCHAR* InDestRoot,
250+
bool bInOverwrite,
251+
int inChunkSizeInKilobytes,
252+
FFileSDKCopyDelegate inProgressCallback,
253+
int inTotalSizeKB
254+
) :
255+
PlatformFile(InPlatformFile),
256+
SourceRoot(InSourceRoot),
257+
DestRoot(InDestRoot),
258+
bOverwrite(bInOverwrite),
259+
chunkSizeInKilobytes(inChunkSizeInKilobytes),
260+
progressCallback(inProgressCallback) {
261+
preInfo.TotalSize = inTotalSizeKB;
262+
preInfo.PriorWritten = 0;
263+
}
264+
265+
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) {
266+
FString NewName(FilenameOrDirectory);
267+
// change the root
268+
NewName = NewName.Replace(SourceRoot, DestRoot);
269+
270+
if (bIsDirectory) {
271+
// create new directory structure
272+
if (!PlatformFile.CreateDirectoryTree(*NewName) && !PlatformFile.DirectoryExists(*NewName)) {
273+
return false;
274+
}
275+
} else {
276+
// Delete destination file if it exists and we are overwriting
277+
if (PlatformFile.FileExists(*NewName) && bOverwrite) {
278+
PlatformFile.DeleteFile(*NewName);
279+
}
280+
281+
// Copy file from source
282+
if (
283+
!UFileSDKBPLibrary::CopyFile(
284+
FilenameOrDirectory,
285+
NewName,
286+
progressCallback,
287+
preInfo,
288+
chunkSizeInKilobytes
289+
)
290+
) {
291+
// Not all files could be copied
292+
return false;
293+
}
294+
295+
if (progressCallback.IsBound()) {
296+
auto statData = PlatformFile.GetStatData(FilenameOrDirectory);
297+
preInfo.PriorWritten += FMath::DivideAndRoundUp(statData.FileSize, int64(1000));
298+
}
299+
}
300+
return true; // continue searching
301+
}
302+
};
303+
304+
// copy files and directories visitor
305+
FCopyFilesAndDirs CopyFilesAndDirs(
306+
PlatformFile,
307+
*SourceDir,
308+
*DestDir,
309+
OverwriteDestination,
310+
ChunkSizeInKilobytes,
311+
ProgressCallback,
312+
FMath::DivideAndRoundUp(StatFilesAndDirs.TotalFileSize, int64(1000))
313+
);
314+
315+
// create all files subdirectories and files in subdirectories!
316+
return PlatformFile.IterateDirectoryRecursively(*SourceDir, CopyFilesAndDirs);
317+
}
318+
319+
void UFileSDKBPLibrary::CopyDirectoryAsync(
320+
FString Source,
321+
FString Destination,
322+
const FFileSDKCopyDelegate & ProgressCallback,
323+
bool OverwriteDestination,
324+
int ChunkSizeInKilobytes
325+
) {
326+
FFunctionGraphTask::CreateAndDispatchWhenReady(
327+
[=] {
328+
UFileSDKBPLibrary::CopyDirectory(
329+
Source,
330+
Destination,
331+
ProgressCallback,
332+
OverwriteDestination,
333+
ChunkSizeInKilobytes
334+
);
335+
},
336+
TStatId(),
337+
nullptr,
338+
ENamedThreads::AnyThread
339+
);
101340
}
102341

103342
bool UFileSDKBPLibrary::ReadStringFromFile(FString FileName, FString & Content) {

Source/FileSDK/Public/FileSDKBPLibrary.h

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,33 @@
1111
#include "Misc/Paths.h"
1212
#include "Misc/FileHelper.h"
1313
#include "HAL/FileManagerGeneric.h"
14+
#include "Async/TaskGraphInterfaces.h"
1415

1516
#if PLATFORM_WINDOWS
1617
#include "Windows/WindowsPlatformMisc.h"
17-
#elif PLATFORM_LINUX
18+
#elif PLATFORM_LINUX || PLATFORM_ANDROID
1819
#include "Unix/UnixPlatformMisc.h"
19-
#elif PLATFORM_MAC
20+
#elif PLATFORM_MAC || PLATFORM_IOS
2021
#include "Apple/ApplePlatformMisc.h"
2122
#endif
2223

2324
#include "FileSDKBPLibrary.generated.h"
2425

26+
UDELEGATE()
27+
DECLARE_DYNAMIC_DELEGATE_TwoParams(FFileSDKCopyDelegate, int, KilobytesWritten, int, TotalKilobytes);
28+
29+
USTRUCT(BlueprintType)
30+
struct FFileSDKDelegatePreInfo {
31+
GENERATED_USTRUCT_BODY();
32+
int PriorWritten;
33+
int TotalSize;
34+
35+
FFileSDKDelegatePreInfo() {
36+
PriorWritten = 0;
37+
TotalSize = 0;
38+
}
39+
};
40+
2541
UCLASS()
2642
class UFileSDKBPLibrary : public UBlueprintFunctionLibrary {
2743
GENERATED_UCLASS_BODY()
@@ -93,24 +109,70 @@ class UFileSDKBPLibrary : public UBlueprintFunctionLibrary {
93109
BlueprintCallable,
94110
meta = (
95111
DisplayName = "Copy File",
96-
Keywords = "FileSDK copy file"
112+
Keywords = "FileSDK copy file",
113+
AutoCreateRefTerm = "ProgressCallback, PreInfo",
114+
HidePin = "PreInfo"
97115
),
98116
Category = "FileSDK"
99117
)
100-
static bool CopyFile(FString Source, FString Destination);
118+
static bool CopyFile(
119+
FString Source,
120+
FString Destination,
121+
const FFileSDKCopyDelegate & ProgressCallback,
122+
FFileSDKDelegatePreInfo PreInfo,
123+
int ChunkSizeInKilobytes = 1024
124+
);
125+
126+
UFUNCTION(
127+
BlueprintCallable,
128+
meta = (
129+
DisplayName = "Copy File Async",
130+
Keywords = "FileSDK copy file async",
131+
AutoCreateRefTerm = "ProgressCallback, PreInfo",
132+
HidePin = "PreInfo"
133+
),
134+
Category = "FileSDK"
135+
)
136+
static void CopyFileAsync(
137+
FString Source,
138+
FString Destination,
139+
const FFileSDKCopyDelegate & ProgressCallback,
140+
FFileSDKDelegatePreInfo PreInfo,
141+
int ChunkSizeInKilobytes = 1024
142+
);
101143

102144
UFUNCTION(
103145
BlueprintCallable,
104146
meta = (
105147
DisplayName = "Copy Directory",
106-
Keywords = "FileSDK copy directory folder"
148+
Keywords = "FileSDK copy directory folder",
149+
AutoCreateRefTerm = "ProgressCallback"
107150
),
108151
Category = "FileSDK"
109152
)
110153
static bool CopyDirectory(
111154
FString Source,
112155
FString Destination,
113-
bool OverwriteDestination = false
156+
const FFileSDKCopyDelegate & ProgressCallback,
157+
bool OverwriteDestination = false,
158+
int ChunkSizeInKilobytes = 1024
159+
);
160+
161+
UFUNCTION(
162+
BlueprintCallable,
163+
meta = (
164+
DisplayName = "Copy Directory Async",
165+
Keywords = "FileSDK copy directory folder async",
166+
AutoCreateRefTerm = "ProgressCallback"
167+
),
168+
Category = "FileSDK"
169+
)
170+
static void CopyDirectoryAsync(
171+
FString Source,
172+
FString Destination,
173+
const FFileSDKCopyDelegate & ProgressCallback,
174+
bool OverwriteDestination = false,
175+
int ChunkSizeInKilobytes = 1024
114176
);
115177

116178
UFUNCTION(

0 commit comments

Comments
 (0)