Added support for deleting resources + minor fixes

This commit is contained in:
Aruki
2019-02-03 02:22:36 -07:00
parent 56843e214d
commit 96c1aae27f
26 changed files with 524 additions and 125 deletions

View File

@@ -297,6 +297,15 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNoti
gpResourceStore = pOldStore;
}
// Create hidden files directory, if needed
TString HiddenDir = pProj->HiddenFilesDir();
if (!FileUtil::Exists(HiddenDir))
{
FileUtil::MakeDirectory(HiddenDir);
FileUtil::MarkHidden(HiddenDir, true);
}
pProj->mpAudioManager->LoadAssets();
pProj->mpTweakManager->LoadTweaks();
return pProj;

View File

@@ -84,6 +84,7 @@ public:
// Directory Handling
inline TString ProjectRoot() const { return mProjectRoot; }
inline TString ProjectPath() const { return mProjectRoot + FileUtil::SanitizeName(mProjectName, false) + ".prj"; }
inline TString HiddenFilesDir() const { return mProjectRoot + ".project/"; }
inline TString DiscDir(bool Relative) const { return Relative ? "Disc/" : mProjectRoot + "Disc/"; }
inline TString PackagesDir(bool Relative) const { return Relative ? "Packages/" : mProjectRoot + "Packages/"; }
inline TString ResourcesDir(bool Relative) const { return Relative ? "Resources/" : mProjectRoot + "Resources/"; }

View File

@@ -113,6 +113,9 @@ bool CResourceEntry::LoadMetadata()
bool CResourceEntry::SaveMetadata(bool ForceSave /*= false*/)
{
// Make sure we aren't saving a deleted resource
ASSERT( !HasFlag(EResEntryFlag::MarkedForDeletion) );
if (mMetadataDirty || ForceSave)
{
TString Path = MetadataFilePath();
@@ -491,6 +494,9 @@ bool CResourceEntry::CanMoveTo(const TString& rkDir, const TString& rkName)
bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir /*= false*/, bool IsAutoGenName /*= false*/)
{
// Make sure we are not moving a deleted resource.
ASSERT( !IsMarkedForDeletion() );
if (!CanMoveTo(rkDir, rkName)) return false;
// Store old paths
@@ -501,7 +507,15 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
TString OldMetaPath = MetadataFilePath();
// Set new directory and name
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, true);
bool DirAlreadyExisted = true;
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, false);
if (!pNewDir)
{
pNewDir = mpStore->GetVirtualDirectory(rkDir, true);
DirAlreadyExisted = false;
}
if (pNewDir == mpDirectory && rkName == mName) return false;
// Check if we can legally move to this spot
@@ -580,7 +594,7 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
// If we succeeded, finish the move
if (FSMoveSuccess)
{
if (mpDirectory != pOldDir)
if (mpDirectory != pOldDir && pOldDir != nullptr)
{
FSMoveSuccess = pOldDir->RemoveChildResource(this);
ASSERT(FSMoveSuccess == true); // this shouldn't be able to fail
@@ -605,7 +619,11 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
errorf("MOVE FAILED: %s", *MoveFailReason);
mpDirectory = pOldDir;
mName = OldName;
mpStore->ConditionalDeleteDirectory(pNewDir, false);
if (!DirAlreadyExisted)
{
mpStore->ConditionalDeleteDirectory(pNewDir, true);
}
if (FileUtil::Exists(NewRawPath))
FileUtil::MoveFile(NewRawPath, OldRawPath);
@@ -627,6 +645,96 @@ bool CResourceEntry::Rename(const TString& rkName, bool IsAutoGenName /*= false*
return MoveAndRename(mpDirectory->FullPath(), rkName, false, IsAutoGenName);
}
void CResourceEntry::MarkDeleted(bool InDeleted)
{
// Flags resource for future deletion. "Deleted" resources remain in memory (which
// allows them to easily be un-deleted) but cannot be looked up in the resource
// store and will not save back out to the resource database. Their file data is
// stored in a temporary directory, which allows them to be moved back if the user
// un-does the deletion.
if (IsMarkedForDeletion() != InDeleted)
{
SetFlagEnabled(EResEntryFlag::MarkedForDeletion, InDeleted);
// Restore old name/directory if un-deleting
if (!InDeleted)
{
// Our directory path is stored in the Name field - see below for explanation
int NameEnd = mName.IndexOf('|');
ASSERT( NameEnd != -1 );
TString DirPath = mName.ChopFront(NameEnd + 1);
mName = mName.ChopBack( mName.Size() - NameEnd);
mpDirectory = mpStore->GetVirtualDirectory( DirPath, true );
ASSERT( mpDirectory != nullptr );
mpDirectory->AddChild("", this);
}
TString CookedPath = CookedAssetPath();
TString RawPath = RawAssetPath();
TString MetaPath = MetadataFilePath();
TString PathBase = mpStore->DeletedResourcePath() + mID.ToString() + ".";
TString DelCookedPath = PathBase + CookedExtension().ToString();
TString DelRawPath = DelCookedPath + ".rsraw";
TString DelMetaPath = DelCookedPath + ".rsmeta";
// If we are deleting...
if (InDeleted)
{
// Temporarily store our directory path in the name string.
// This is a hack, but we can't store the directory pointer because it may have been
// deleted and remade by the user by the time the resource is un-deleted, which
// means it is not safe to access later. Separating the name and the path with
// the '|' character is safe because this character is not allowed in filenames
// (which is enforced in FileUtil::IsValidName()).
mName = mName + "|" + mpDirectory->FullPath();
// Remove from parent directory.
mpDirectory->RemoveChildResource(this);
mpDirectory = nullptr;
// Move any resource files out of the project into a temporary folder.
FileUtil::MakeDirectory(DelMetaPath.GetFileDirectory());
if (FileUtil::Exists(MetaPath))
{
FileUtil::MoveFile(MetaPath, DelMetaPath);
}
if (FileUtil::Exists(RawPath))
{
FileUtil::MoveFile(RawPath, DelRawPath);
}
if (FileUtil::Exists(CookedPath))
{
FileUtil::MoveFile(CookedPath, DelCookedPath);
}
}
// If we are un-deleting...
else
{
// Move any resource files out of the temporary folder back into the project.
FileUtil::MakeDirectory(MetaPath.GetFileDirectory());
if (FileUtil::Exists(DelMetaPath))
{
FileUtil::MoveFile(DelMetaPath, MetaPath);
}
if (FileUtil::Exists(DelRawPath))
{
FileUtil::MoveFile(DelRawPath, RawPath);
}
if (FileUtil::Exists(DelCookedPath))
{
FileUtil::MoveFile(DelCookedPath, CookedPath);
}
}
mpStore->SetCacheDirty();
debugf("%s FOR DELETION: [%s] %s", InDeleted ? "MARKED" : "UNMARKED", *ID().ToString(), *CookedPath.GetFileName());
}
}
CGameProject* CResourceEntry::Project() const
{
return mpStore ? mpStore->Project() : nullptr;

View File

@@ -21,6 +21,7 @@ enum class EResEntryFlag
HasBeenModified = 0x00000008, // Resource has been modified and resaved by the user
AutoResName = 0x00000010, // Resource name is auto-generated
AutoResDir = 0x00000020, // Resource directory name is auto-generated
MarkedForDeletion = 0x00000040, // Resource has been marked for deletion by the user
};
DECLARE_FLAGS(EResEntryFlag, FResEntryFlags)
@@ -76,6 +77,7 @@ public:
bool MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir = false, bool IsAutoGenName = false);
bool Move(const TString& rkDir, bool IsAutoGenDir = false);
bool Rename(const TString& rkName, bool IsAutoGenName = false);
void MarkDeleted(bool InDeleted);
CGameProject* Project() const;
EGame Game() const;
@@ -90,6 +92,7 @@ public:
inline void SetHidden(bool Hidden) { SetFlagEnabled(EResEntryFlag::Hidden, Hidden); }
inline bool HasFlag(EResEntryFlag Flag) const { return mFlags.HasFlag(Flag); }
inline bool IsHidden() const { return HasFlag(EResEntryFlag::Hidden); }
inline bool IsMarkedForDeletion() const { return HasFlag(EResEntryFlag::MarkedForDeletion); }
inline bool IsLoaded() const { return mpResource != nullptr; }
inline bool IsCategorized() const { return mpDirectory && !mpDirectory->FullPath().CaseInsensitiveCompare( mpStore->DefaultResourceDirPath() ); }

View File

@@ -22,12 +22,16 @@ public:
virtual CResourceEntry* Next()
{
if (mIter != mpkStore->mResourceEntries.end())
do
{
mpCurEntry = mIter->second;
mIter++;
if (mIter != mpkStore->mResourceEntries.end())
{
mpCurEntry = mIter->second;
mIter++;
}
else mpCurEntry = nullptr;
}
else mpCurEntry = nullptr;
while (mpCurEntry && mpCurEntry->IsMarkedForDeletion());
return mpCurEntry;
}

View File

@@ -66,6 +66,22 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
{
// Serialize resources
uint32 ResourceCount = mResourceEntries.size();
if (rArc.IsWriter())
{
// Make sure deleted resources aren't included in the count.
// We can't use CResourceIterator because it skips MarkedForDeletion resources.
for (auto Iter = mResourceEntries.begin(); Iter != mResourceEntries.end(); Iter++)
{
CResourceEntry* pEntry = Iter->second;
if (pEntry->IsMarkedForDeletion())
{
ResourceCount--;
}
}
}
rArc << SerialParameter("ResourceCount", ResourceCount);
if (rArc.IsReader())
@@ -85,10 +101,13 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
{
for (CResourceIterator It(this); It; ++It)
{
if (rArc.ParamBegin("Resource", 0))
if (!It->IsMarkedForDeletion())
{
It->SerializeEntryInfo(rArc, false);
rArc.ParamEnd();
if (rArc.ParamBegin("Resource", 0))
{
It->SerializeEntryInfo(rArc, false);
rArc.ParamEnd();
}
}
}
}
@@ -140,18 +159,22 @@ bool CResourceStore::LoadDatabaseCache()
}
else
{
// Database is succesfully loaded at this point
// Database is successfully loaded at this point
if (mpProj)
{
ASSERT(mpProj->Game() == Reader.Game());
}
mGame = Reader.Game();
}
mGame = Reader.Game();
return true;
}
bool CResourceStore::SaveDatabaseCache()
{
TString Path = DatabasePath();
debugf("Saving database cache...");
CBasicBinaryWriter Writer(Path, FOURCC('CACH'), 0, mGame);
@@ -182,6 +205,14 @@ void CResourceStore::SetProject(CGameProject *pProj)
mDatabasePath = mpProj->ProjectRoot();
mpDatabaseRoot = new CVirtualDirectory(this);
mGame = mpProj->Game();
// Clear deleted files from previous runs
TString DeletedPath = DeletedResourcePath();
if (FileUtil::Exists(DeletedPath))
{
FileUtil::ClearDirectory(DeletedPath);
}
}
}
@@ -215,6 +246,14 @@ void CResourceStore::CloseProject()
It = mResourceEntries.erase(It);
}
// Clear deleted files from previous runs
TString DeletedPath = DeletedResourcePath();
if (FileUtil::Exists(DeletedPath))
{
FileUtil::ClearDirectory(DeletedPath);
}
delete mpDatabaseRoot;
mpDatabaseRoot = nullptr;
mpProj = nullptr;
@@ -256,12 +295,27 @@ TString CResourceStore::DefaultResourceDirPath() const
return StaticDefaultResourceDirPath( mGame );
}
TString CResourceStore::DeletedResourcePath() const
{
return mpProj->HiddenFilesDir() / "delete/";
}
CResourceEntry* CResourceStore::FindEntry(const CAssetID& rkID) const
{
if (!rkID.IsValid()) return nullptr;
auto Found = mResourceEntries.find(rkID);
if (Found == mResourceEntries.end()) return nullptr;
else return Found->second;
if (rkID.IsValid())
{
auto Found = mResourceEntries.find(rkID);
if (Found != mResourceEntries.end())
{
CResourceEntry* pEntry = Found->second;
if (!pEntry->IsMarkedForDeletion())
return pEntry;
}
}
return nullptr;
}
CResourceEntry* CResourceStore::FindEntry(const TString& rkPath) const
@@ -411,6 +465,8 @@ CResourceEntry* CResourceStore::CreateNewResource(const CAssetID& rkID, EResourc
{
TrackLoadedResource(pEntry);
}
debugf("CREATED NEW RESOURCE: [%s] %s", *rkID.ToString(), *pEntry->CookedAssetPath());
}
else

View File

@@ -52,6 +52,7 @@ public:
void CreateVirtualDirectory(const TString& rkPath);
void ConditionalDeleteDirectory(CVirtualDirectory *pDir, bool Recurse);
TString DefaultResourceDirPath() const;
TString DeletedResourcePath() const;
bool IsResourceRegistered(const CAssetID& rkID) const;
CResourceEntry* CreateNewResource(const CAssetID& rkID, EResourceType Type, const TString& rkDir, const TString& rkName);

View File

@@ -46,6 +46,28 @@ bool CVirtualDirectory::IsDescendantOf(CVirtualDirectory *pDir) const
return (this == pDir) || (mpParent && pDir && (mpParent == pDir || mpParent->IsDescendantOf(pDir)));
}
bool CVirtualDirectory::IsSafeToDelete() const
{
// Return false if we contain any referenced assets.
for (CResourceEntry* pEntry : mResources)
{
if (pEntry->IsLoaded() && pEntry->Resource()->IsReferenced())
{
return false;
}
}
for (CVirtualDirectory* pSubdir : mSubdirectories)
{
if (!pSubdir->IsSafeToDelete())
{
return false;
}
}
return true;
}
TString CVirtualDirectory::FullPath() const
{
if (IsRoot())

View File

@@ -26,6 +26,7 @@ public:
bool IsEmpty(bool CheckFilesystem) const;
bool IsDescendantOf(CVirtualDirectory *pDir) const;
bool IsSafeToDelete() const;
TString FullPath() const;
TString AbsolutePath() const;
CVirtualDirectory* GetRoot();