6#include "Algo/IndexOf.h"
7#include "Async/Async.h"
8#include "Async/TaskGraphInterfaces.h"
9#include "Containers/Array.h"
10#include "Containers/ArrayView.h"
11#include "Containers/Map.h"
12#include "Containers/SparseArray.h"
13#include "Containers/UnrealString.h"
16#include "GenericPlatform/GenericPlatformFile.h"
17#include "HAL/CriticalSection.h"
18#include "HAL/PlatformCrt.h"
19#include "HAL/PlatformString.h"
20#include "Logging/LogCategory.h"
21#include "Logging/LogMacros.h"
22#include "Math/NumericLimits.h"
23#include "Math/UnrealMathUtility.h"
24#include "Misc/AssertionMacros.h"
25#include "Misc/CommandLine.h"
26#include "Misc/ConfigCacheIni.h"
27#include "Misc/EnumClassFlags.h"
28#include "Misc/Optional.h"
29#include "Misc/Parse.h"
30#include "Misc/Paths.h"
31#include "Misc/ScopeLock.h"
32#include "Misc/ScopeRWLock.h"
33#include "Templates/UniquePtr.h"
34#include "Templates/UnrealTemplate.h"
35#include "Trace/Detail/Channel.h"
74 FLockData& LockData = FileLockMap.FindOrAddByHash(KeyHash, InFilename);
77 return LockData.CS.Get();
86 FLockData* LockData = FileLockMap.FindByHash(KeyHash, InFilename);
93 FileLockMap.RemoveByHash(KeyHash, InFilename);
150 check(OptionalStorageQuota < StorageQuota || StorageQuota <= 0);
183 if (ActualStorageQuota >= 0)
185 if (!bIncludeOptionalStorage)
194 return ActualStorageQuota - UsedQuota;
211 int64 OldFileSize = 0;
214 int64* pOldFileSize = FileSizes.FindByHash(KeyHash, Filename);
217 OldFileSize = *pOldFileSize;
225 FileSizes.AddByHash(KeyHash, Filename, FileSize);
227 UE_LOG(LogPlatformFileManagedStorage, Verbose,
TEXT(
"File %s is added to category %s"), *Filename, *CategoryName);
238 if (FileSizes.RemoveAndCopyValue(Filename, OldSize))
242 UsedQuota -= OldSize;
244 UE_LOG(LogPlatformFileManagedStorage, Verbose,
TEXT(
"File %s is removed from category %s"), *Filename, *CategoryName);
261 return FString::Printf(
TEXT(
"Category %s: %.3f MiB (%" INT64_FMT ") / unlimited used"), *CategoryName, (
float)UsedSize / 1024.f / 1024.f, UsedSize);
265 return FString::Printf(
TEXT(
"Category %s: %.3f MiB (%" INT64_FMT ") / %.3f MiB used"), *CategoryName, (
float)UsedSize / 1024.f / 1024.f, UsedSize, (
float)TotalSize / 1024.f / 1024.f);
280 return CategoryStat{ CategoryName, UsedQuota, StorageQuota, FileSizes, Directories };
288 check(OldFileSize >= 0);
289 check(NewFileSize >= 0);
291 if (NewFileSize <= OldFileSize)
293 UsedQuota -= (OldFileSize - NewFileSize);
304 int64 OldUsedQuota = UsedQuota;
308 NewUsedQuota = OldUsedQuota + (NewFileSize - OldFileSize);
309 if (NewUsedQuota > StorageQuota)
311 return EPersistentStorageManagerFileSizeFlags::RespectQuota;
313 }
while (!UsedQuota.compare_exchange_weak(OldUsedQuota, NewUsedQuota));
318 UsedQuota += (NewFileSize - OldFileSize);
324 for (
const FString& Directory : Directories)
326 if (ManagedStorageInternal::IsUnderDirectory(Filename, Directory))
377 AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, []()
379 class FInitStorageVisitor :
public IPlatformFile::FDirectoryVisitor
382 FInitStorageVisitor() : IPlatformFile::FDirectoryVisitor(EDirectoryVisitorFlags::ThreadSafe)
385 virtual bool Visit(
const TCHAR* FilenameOrDirectory,
bool bIsDirectory)
387 if (FilenameOrDirectory && !bIsDirectory)
389 FPersistentStorageManager& Man = FPersistentStorageManager::Get();
390 if (FPersistentManagedFile File = Man.TryManageFile(FilenameOrDirectory))
392 FManagedStorageScopeFileLock ScopeFileLock(File);
395 FFileStatData StatData = IPlatformFile::GetPlatformPhysical().GetStatData(FilenameOrDirectory);
400 UE_LOG(LogPlatformFileManagedStorage, Error,
TEXT(
"Invalid File Size for %s!"), FilenameOrDirectory);
403 Man.AddOrUpdateFile(File, StatData.FileSize);
407 UE_LOG(LogPlatformFileManagedStorage, Error,
TEXT(
"Invalid Stat Data for %s!"), FilenameOrDirectory);
416 FInitStorageVisitor Visitor;
418 for (
const FString& RootDir : FPersistentStorageManager::Get().GetRootDirectories())
420 UE_LOG(LogPlatformFileManagedStorage, Display,
TEXT(
"Scan directory %s"), *RootDir);
422 IPlatformFile::GetPlatformPhysical().IterateDirectoryRecursively(*RootDir, Visitor);
425 UE_LOG(LogPlatformFileManagedStorage, Display,
TEXT(
"Done scanning root directories"));
444 OutFile.FullFilename =
FPaths::ConvertRelativePathToFull(MoveTemp(Filename));
454 int32 CategoryIndex = 0;
455 for (
const FPersistentStorageCategory& Category : Categories.GetCategories())
457 if (Category.IsInCategory(OutFile.FullFilename))
459 OutFile.Category = CategoryIndex;
465 bool bIsUnderRootDir = !!Algo::FindByPredicate(RootDirectories.GetRootDirectories(), [&OutFile](
const FString& RootDir) {
return ManagedStorageInternal::IsUnderDirectory(OutFile.FullFilename, RootDir); });
468 OutFile.Category = Categories.GetDefaultCategoryIndex();
481 return Categories.GetCategories()[File.Category].AddOrUpdateFile(File.FullFilename, FileSize, Flags);
491 return Categories.GetCategories()[File.Category].RemoveFile(File.FullFilename);
497 int64 TotalUsedSize = 0LL;
498 for(
const FPersistentStorageCategory& Category : Categories.GetCategories())
500 TotalUsedSize += Category.GetUsedSize();
503 return TotalUsedSize;
517 FString FullPath =
FPaths::ConvertRelativePathToFull(MoveTemp(Path));
519 for (
const FPersistentStorageCategory& Category : Categories.GetCategories())
521 if (Category.IsInCategory(FullPath))
523 UsedSpace = Category.GetUsedSize();
524 RemainingSpace = Category.GetAvailableSize();
525 Quota = Category.GetCategoryQuota();
526 if (OptionalQuota !=
nullptr)
528 *OptionalQuota = Category.GetCategoryOptionalQuota();
538 FPersistentStorageCategory* Category = Algo::FindBy(Categories.GetCategories(), InCategory, [](
const FPersistentStorageCategory& Cat) {
return Cat.GetCategoryName(); });
544 if (OptionalQuota !=
nullptr)
561 for (
const FPersistentStorageCategory& Category : Categories.GetCategories())
563 UsedSpace += Category.GetUsedSize();
564 RequiredSpace += Category.GetCategoryQuota();
565 OptionalSpace += Category.GetCategoryOptionalQuota();
582 return Categories.GetCategories()[File.Category].IsCategoryFull();
587 TMap<FString, FPersistentStorageCategory::CategoryStat> CategoryStats;
588 CategoryStats.Reserve(Categories.GetCategories().Num());
590 for (
const FPersistentStorageCategory& Category : Categories.GetCategories())
592 CategoryStats.Add(Category.GetCategoryName(), Category.GetStat());
595 return CategoryStats;
602 for (
const FPersistentStorageCategory& Category : Categories.GetCategories())
604 if (Category.GetCategoryName() == InCategory)
606 Result.Emplace(Category.GetStat());
629 void Init(TArrayView<
const FPersistentStorageCategory> Categories);
642 FCategoryInfo(TArray<FPersistentStorageCategory>&& InCategories, int32 InDefaultCategory);
669 if (!File && IsReady())
671 File = FPersistentStorageManager::Get().TryManageFile(File.FullFilename);
674 return File.IsValid();
693 FPersistentStorageManager::Get().AddOrUpdateFile(File, FileHandle->Size());
698 return FileHandle->Tell();
701 virtual bool Seek(int64 NewPosition)
override
703 return FileHandle->Seek(NewPosition);
706 virtual bool SeekFromEnd(int64 NewPositionRelativeToEnd = 0)
override
708 return FileHandle->SeekFromEnd(NewPositionRelativeToEnd);
711 virtual bool Read(uint8* Destination, int64 BytesToRead)
override
713 return FileHandle->Read(Destination, BytesToRead);
716 virtual bool Write(
const uint8* Source, int64 BytesToWrite)
override
720 return FileHandle->Write(Source, BytesToWrite);
727 int64 NewSize = FMath::Max(FileHandle->Tell() + BytesToWrite, FileHandle->Size());
731 if (bIsFileCategoryFull)
733 UE_LOG(LogPlatformFileManagedStorage, Error,
TEXT(
"Failed to write to file %s. The category of the file has reach quota limit in peristent storage."), *File.FullFilename);
737 bool bSuccess = FileHandle->Write(Source, BytesToWrite);
740 int64 FileSize = FileHandle->Size();
743 verify(Manager.AddOrUpdateFile(File, FileSize) == EPersistentStorageManagerFileSizeFlags::None);
752 return FileHandle->Size();
755 virtual bool Flush(
const bool bFullFlush =
false)
override
757 const bool bOldIsValid = File.IsValid();
760 return FileHandle->Flush(bFullFlush);
765 const bool bSuccess = FileHandle->Flush(bFullFlush);
766 const bool bForceSizeUpdate = !bOldIsValid;
767 if (!bSuccess || bForceSizeUpdate)
769 int64 FileSize = FileHandle->Size();
772 verify(FPersistentStorageManager::Get().AddOrUpdateFile(File, FileSize) == EPersistentStorageManagerFileSizeFlags::None);
783 return FileHandle->Truncate(NewSize);
790 int64 FileSize = FileHandle->Size();
792 if (NewSize <= FileSize)
794 bool bSuccess = FileHandle->Truncate(NewSize);
795 FileSize = FileHandle->Size();
796 verify(Manager.AddOrUpdateFile(File, FileSize) == EPersistentStorageManagerFileSizeFlags::None);
800 if (Manager.AddOrUpdateFile(File, NewSize, EPersistentStorageManagerFileSizeFlags::RespectQuota) != EPersistentStorageManagerFileSizeFlags::None)
802 UE_LOG(LogPlatformFileManagedStorage, Error,
TEXT(
"Failed to truncate file %s. The category of the file has reach quota limit in peristent storage."), *File.FullFilename);
806 if (!FileHandle->Truncate(NewSize))
808 FileSize = FileHandle->Size();
809 verify(Manager.AddOrUpdateFile(File, FileSize) == EPersistentStorageManagerFileSizeFlags::None);
818 FileHandle->ShrinkBuffers();
828template<
class BaseClass>
861 virtual bool DeleteFile(
const TCHAR* Filename)
override
883 virtual bool MoveFile(
const TCHAR* To,
const TCHAR* From)
override
#define ensureAlways( InExpression)
#define LowLevelFatalError(Format,...)
FConfigCacheIni * GConfig
#define ENUM_CLASS_FLAGS(Enum)
EPersistentStorageManagerFileSizeFlags
FWindowsCriticalSection FCriticalSection
void ReleaseLock(const FString &InFilename)
FCriticalSection * GetLock(const FString &InFilename)
FCriticalSection FileLockMapCS
TMap< FString, FLockData > FileLockMap
TUniquePtr< IFileHandle > FileHandle
virtual int64 Tell() override
virtual int64 Size() override
FPersistentManagedFile File
virtual ~FManagedStorageFileWriteHandle()
virtual void ShrinkBuffers() override
virtual bool SeekFromEnd(int64 NewPositionRelativeToEnd=0) override
virtual bool Write(const uint8 *Source, int64 BytesToWrite) override
FManagedStorageFileWriteHandle(IFileHandle *InFileHandle, FPersistentManagedFile InFile)
virtual bool Truncate(int64 NewSize) override
virtual bool Flush(const bool bFullFlush=false) override
virtual bool Seek(int64 NewPosition) override
virtual bool Read(uint8 *Destination, int64 BytesToRead) override
FManagedStorageScopeFileLock(FPersistentManagedFile InManagedFile)
FCriticalSection * pFileCS
FPersistentManagedFile ManagedFile
~FManagedStorageScopeFileLock()
static FString ConvertRelativePathToFull(const FString &InPath)
static FCategoryInfo InitCategories()
void TryManageFileInternal(FPersistentManagedFile &OutFile)
FRootDirInfo RootDirectories
FPersistentManagedFile TryManageFile(FString &&Filename)
int64 GetTotalUsedSize() const
bool IsInitialized() const
EPersistentStorageManagerFileSizeFlags AddOrUpdateFile(const FPersistentManagedFile &File, const int64 FileSize, EPersistentStorageManagerFileSizeFlags Flags=EPersistentStorageManagerFileSizeFlags::None)
TMap< FString, FPersistentStorageCategory::CategoryStat > GenerateCategoryStats() const
static FPersistentStorageManager & Get()
bool GetPersistentStorageUsageByCategory(const FString &InCategory, int64 &UsedSpace, int64 &RemainingSpace, int64 &Quota, int64 *OptionalQuota=nullptr)
FPersistentStorageManager()
TArrayView< const FString > GetRootDirectories() const
bool IsCategoryForFileFull(const FPersistentManagedFile &File) const
FPersistentManagedFile TryManageFile(const FString &Filename)
TOptional< FPersistentStorageCategory::CategoryStat > GetCategoryStat(const FString &InCategory) const
bool GetPersistentStorageSize(int64 &UsedSpace, int64 &RequiredSpace, int64 &OptionalSpace) const
Returns any additional required free space and optional free space.
bool GetPersistentStorageUsage(FString Path, int64 &UsedSpace, int64 &RemainingSpace, int64 &Quota, int64 *OptionalQuota=nullptr)
bool RemoveFileFromManager(FPersistentManagedFile &File)
UE_NODISCARD_CTOR FReadScopeLock(FRWLock &InLock)
UE_NODISCARD_CTOR FScopeLock(FCriticalSection *InSynchObject)
friend FORCEINLINE uint32 GetTypeHash(const FString &S)
FORCEINLINE void WriteLock()
FORCEINLINE void WriteUnlock()
UE_NODISCARD_CTOR FWriteScopeLock(FRWLock &InLock)
bool IsUnderDirectory(const FString &InPath, const FString &InDirectory)
static bool IsInitialized()
TUniquePtr< FCriticalSection > CS
TMap< FString, int64 > FileSizes
TArray< FString > Directories
bool IsInCategory(const FString &Path) const
const FString & GetCategoryName() const
std::atomic< int64 > UsedQuota
const int64 OptionalStorageQuota
int64 GetUsedSize() const
int64 GetCategoryQuota(bool bIncludeOptional=true) const
int64 GetCategoryOptionalQuota() const
FPersistentStorageCategory(FString InCategoryName, TArray< FString > InDirectories, const int64 InQuota, const int64 InOptionalQuota)
TMap< FString, int64 > FileSizes
const TArray< FString > Directories
FManagedStorageFileLockRegistry & GetLockRegistry()
CategoryStat GetStat() const
int64 GetAvailableSize(bool bIncludeOptionalStorage=true) const
EPersistentStorageManagerFileSizeFlags AddOrUpdateFile(const FString &Filename, const int64 FileSize, EPersistentStorageManagerFileSizeFlags Flags)
bool RemoveFile(const FString &Filename)
EPersistentStorageManagerFileSizeFlags TryUpdateQuota(const int64 OldFileSize, const int64 NewFileSize, EPersistentStorageManagerFileSizeFlags Flags)
const FString CategoryName
const TArray< FString > & GetDirectories() const
FManagedStorageFileLockRegistry FileLockRegistry
bool ShouldManageFile(const FString &Filename) const
bool IsCategoryFull() const
TArray< FPersistentStorageCategory > Categories
int32 GetDefaultCategoryIndex() const
const FPersistentStorageCategory * GetDefaultCategory() const
FCategoryInfo(TArray< FPersistentStorageCategory > &&InCategories, int32 InDefaultCategory)
TArrayView< FPersistentStorageCategory > GetCategories()
FPersistentStorageCategory * GetDefaultCategory()
TArrayView< const FPersistentStorageCategory > GetCategories() const
void Init(TArrayView< const FPersistentStorageCategory > Categories)
TArray< FString > RootDirectories
TArrayView< const FString > GetRootDirectories() const