mirror of
https://github.com/AxioDL/PrimeWorldEditor.git
synced 2025-05-24 08:11:36 +00:00
769 lines
23 KiB
C++
769 lines
23 KiB
C++
#include "CResourceEntry.h"
|
|
#include "CGameProject.h"
|
|
#include "CResourceStore.h"
|
|
#include "Core/Resource/CResource.h"
|
|
#include "Core/Resource/Cooker/CResourceCooker.h"
|
|
#include "Core/Resource/Factory/CResourceFactory.h"
|
|
#include <Common/FileIO.h>
|
|
#include <Common/FileUtil.h>
|
|
#include <Common/TString.h>
|
|
#include <Common/Serialization/CXMLReader.h>
|
|
#include <Common/Serialization/CXMLWriter.h>
|
|
|
|
CResourceEntry::CResourceEntry(CResourceStore *pStore)
|
|
: mpResource(nullptr)
|
|
, mpTypeInfo(nullptr)
|
|
, mpStore(pStore)
|
|
, mpDependencies(nullptr)
|
|
, mID( CAssetID::InvalidID(pStore->Game()) )
|
|
, mpDirectory(nullptr)
|
|
, mMetadataDirty(false)
|
|
, mCachedSize(-1)
|
|
{}
|
|
|
|
// Static constructors
|
|
CResourceEntry* CResourceEntry::CreateNewResource(CResourceStore *pStore, const CAssetID& rkID,
|
|
const TString& rkDir, const TString& rkName,
|
|
EResourceType Type, bool ExistingResource /*= false*/)
|
|
{
|
|
// Initialize all entry info with the input data.
|
|
CResourceEntry *pEntry = new CResourceEntry(pStore);
|
|
pEntry->mID = rkID;
|
|
pEntry->mName = rkName;
|
|
pEntry->mCachedUppercaseName = rkName.ToUpper();
|
|
|
|
pEntry->mpTypeInfo = CResTypeInfo::FindTypeInfo(Type);
|
|
ASSERT(pEntry->mpTypeInfo);
|
|
|
|
pEntry->mpDirectory = pStore->GetVirtualDirectory(rkDir, true);
|
|
ASSERT(pEntry->mpDirectory);
|
|
pEntry->mpDirectory->AddChild("", pEntry);
|
|
|
|
pEntry->mMetadataDirty = true;
|
|
|
|
// If this is a new resource (i.e. not a base game resource that we are currently exporting),
|
|
// then instantiate the new resource data so it can be saved as soon as possible.
|
|
if (!ExistingResource)
|
|
{
|
|
pEntry->mpResource = CResourceFactory::CreateResource(pEntry);
|
|
|
|
if (pEntry->mpResource)
|
|
{
|
|
pEntry->mpResource->InitializeNewResource();
|
|
}
|
|
}
|
|
|
|
return pEntry;
|
|
}
|
|
|
|
CResourceEntry* CResourceEntry::BuildFromArchive(CResourceStore *pStore, IArchive& rArc)
|
|
{
|
|
// Load all entry info from the archive.
|
|
CResourceEntry *pEntry = new CResourceEntry(pStore);
|
|
pEntry->SerializeEntryInfo(rArc, false);
|
|
ASSERT(pEntry->mpTypeInfo);
|
|
ASSERT(pEntry->mpDirectory);
|
|
return pEntry;
|
|
}
|
|
|
|
CResourceEntry* CResourceEntry::BuildFromDirectory(CResourceStore *pStore, CResTypeInfo *pTypeInfo,
|
|
const TString& rkDirPath, const TString& rkName)
|
|
{
|
|
// Initialize as much entry info as possible from the input data, then load the rest from the metadata file.
|
|
ASSERT(pTypeInfo);
|
|
|
|
CResourceEntry *pEntry = new CResourceEntry(pStore);
|
|
pEntry->mpTypeInfo = pTypeInfo;
|
|
pEntry->mName = rkName;
|
|
pEntry->mCachedUppercaseName = rkName.ToUpper();
|
|
|
|
pEntry->mpDirectory = pStore->GetVirtualDirectory(rkDirPath, true);
|
|
ASSERT(pEntry->mpDirectory);
|
|
pEntry->mpDirectory->AddChild("", pEntry);
|
|
|
|
// Make sure we're valid, then load the remaining data from the metadata file
|
|
ASSERT(pEntry->HasCookedVersion() || pEntry->HasRawVersion());
|
|
bool Success = pEntry->LoadMetadata();
|
|
ASSERT(Success);
|
|
|
|
return pEntry;
|
|
}
|
|
|
|
CResourceEntry::~CResourceEntry()
|
|
{
|
|
if (mpResource) delete mpResource;
|
|
if (mpDependencies) delete mpDependencies;
|
|
}
|
|
|
|
bool CResourceEntry::LoadMetadata()
|
|
{
|
|
ASSERT(!mMetadataDirty);
|
|
|
|
TString Path = MetadataFilePath();
|
|
CBinaryReader MetaFile(Path, FOURCC('META'));
|
|
|
|
if (MetaFile.IsValid())
|
|
{
|
|
SerializeEntryInfo(MetaFile, true);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
errorf("%s: Failed to load metadata file!", *Path);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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();
|
|
TString Dir = Path.GetFileDirectory();
|
|
FileUtil::MakeDirectory(Dir);
|
|
|
|
CBinaryWriter MetaFile(Path, FOURCC('META'), 0, Game());
|
|
|
|
if (MetaFile.IsValid())
|
|
{
|
|
SerializeEntryInfo(MetaFile, true);
|
|
mMetadataDirty = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CResourceEntry::SerializeEntryInfo(IArchive& rArc, bool MetadataOnly)
|
|
{
|
|
CAssetID ID = mID;
|
|
|
|
rArc << SerialParameter("AssetID", ID)
|
|
<< SerialParameter("Type", mpTypeInfo)
|
|
<< SerialParameter("Flags", mFlags);
|
|
|
|
// Don't allow the file to override our asset ID if we already have a valid one.
|
|
if (rArc.IsReader() && !mID.IsValid())
|
|
mID = ID;
|
|
|
|
// Serialize extra data that we exclude from the metadata file
|
|
if (!MetadataOnly)
|
|
{
|
|
TString Dir = (mpDirectory ? mpDirectory->FullPath() : "");
|
|
|
|
rArc << SerialParameter("Name", mName)
|
|
<< SerialParameter("Directory", Dir)
|
|
<< SerialParameter("Dependencies", mpDependencies);
|
|
|
|
if (rArc.IsReader())
|
|
{
|
|
mpDirectory = mpStore->GetVirtualDirectory(Dir, true);
|
|
mpDirectory->AddChild("", this);
|
|
mCachedUppercaseName = mName.ToUpper();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CResourceEntry::UpdateDependencies()
|
|
{
|
|
if (mpDependencies)
|
|
{
|
|
delete mpDependencies;
|
|
mpDependencies = nullptr;
|
|
}
|
|
|
|
if (!mpTypeInfo->CanHaveDependencies())
|
|
{
|
|
mpDependencies = new CDependencyTree();
|
|
return;
|
|
}
|
|
|
|
bool WasLoaded = IsLoaded();
|
|
|
|
if (!mpResource)
|
|
Load();
|
|
|
|
if (!mpResource)
|
|
{
|
|
errorf("Unable to update cached dependencies; failed to load resource");
|
|
mpDependencies = new CDependencyTree();
|
|
return;
|
|
}
|
|
|
|
mpDependencies = mpResource->BuildDependencyTree();
|
|
mpStore->SetCacheDirty();
|
|
|
|
if (!WasLoaded)
|
|
mpStore->DestroyUnreferencedResources();
|
|
}
|
|
|
|
bool CResourceEntry::HasRawVersion() const
|
|
{
|
|
return FileUtil::Exists(RawAssetPath());
|
|
}
|
|
|
|
bool CResourceEntry::HasCookedVersion() const
|
|
{
|
|
return FileUtil::Exists(CookedAssetPath());
|
|
}
|
|
|
|
TString CResourceEntry::RawAssetPath(bool Relative) const
|
|
{
|
|
return CookedAssetPath(Relative) + ".rsraw";
|
|
}
|
|
|
|
TString CResourceEntry::RawExtension() const
|
|
{
|
|
return CookedExtension().ToString() + ".rsraw";
|
|
}
|
|
|
|
TString CResourceEntry::CookedAssetPath(bool Relative) const
|
|
{
|
|
TString Ext = CookedExtension().ToString();
|
|
TString Path = mpDirectory ? mpDirectory->FullPath() : "";
|
|
TString Name = mName + "." + Ext;
|
|
return Relative ? Path + Name : mpStore->ResourcesDir() + Path + Name;
|
|
}
|
|
|
|
CFourCC CResourceEntry::CookedExtension() const
|
|
{
|
|
return mpTypeInfo->CookedExtension(Game());
|
|
}
|
|
|
|
TString CResourceEntry::MetadataFilePath(bool Relative) const
|
|
{
|
|
return CookedAssetPath(Relative) + ".rsmeta";
|
|
}
|
|
|
|
bool CResourceEntry::IsInDirectory(CVirtualDirectory *pDir) const
|
|
{
|
|
CVirtualDirectory *pParentDir = mpDirectory;
|
|
|
|
while (pParentDir)
|
|
{
|
|
if (pParentDir == pDir) return true;
|
|
pParentDir = pParentDir->Parent();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint64 CResourceEntry::Size() const
|
|
{
|
|
if (mCachedSize == -1)
|
|
{
|
|
if (HasCookedVersion())
|
|
mCachedSize = FileUtil::FileSize(CookedAssetPath());
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
return mCachedSize;
|
|
}
|
|
|
|
bool CResourceEntry::NeedsRecook() const
|
|
{
|
|
// Assets that do not have a raw version can't be recooked since they will always just be saved cooked to begin with.
|
|
// We will recook any asset where the raw version has been updated but not recooked yet. eREF_NeedsRecook can also be
|
|
// toggled to arbitrarily flag any asset for recook.
|
|
if (!HasRawVersion()) return false;
|
|
if (!HasCookedVersion()) return true;
|
|
if (HasFlag(EResEntryFlag::NeedsRecook)) return true;
|
|
return (FileUtil::LastModifiedTime(CookedAssetPath()) < FileUtil::LastModifiedTime(RawAssetPath()));
|
|
}
|
|
|
|
bool CResourceEntry::Save(bool SkipCacheSave /*= false*/, bool FlagForRecook /*= true*/)
|
|
{
|
|
// SkipCacheSave argument tells us not to save the resource cache file. This is generally not advised because we don't
|
|
// want the actual resource data to desync from the cache data. However, there are occasions where we save multiple
|
|
// resources at a time and in that case it's preferable to only save the cache once. If you do set SkipCacheSave to true,
|
|
// then make sure you manually update the cache file afterwards.
|
|
//
|
|
// For now, always save the resource when this function is called even if there's been no changes made to it in memory.
|
|
// In the future this might not be desired behavior 100% of the time.
|
|
bool ShouldCollectGarbage = false;
|
|
|
|
// Save raw resource
|
|
if (mpTypeInfo->CanBeSerialized())
|
|
{
|
|
ShouldCollectGarbage = !IsLoaded();
|
|
|
|
Load();
|
|
if (!mpResource) return false;
|
|
|
|
// Note: We call Serialize directly for resources to avoid having a redundant resource root node in the output file.
|
|
TString Path = RawAssetPath();
|
|
TString Dir = Path.GetFileDirectory();
|
|
FileUtil::MakeDirectory(Dir);
|
|
|
|
TString SerialName = mpTypeInfo->TypeName();
|
|
SerialName.RemoveWhitespace();
|
|
|
|
CXMLWriter Writer(Path, SerialName, 0, Game());
|
|
mpResource->Serialize(Writer);
|
|
|
|
if (!Writer.Save())
|
|
{
|
|
errorf("Failed to save raw resource: %s", *Path);
|
|
return false;
|
|
}
|
|
|
|
if (FlagForRecook)
|
|
{
|
|
SetFlag(EResEntryFlag::NeedsRecook);
|
|
}
|
|
}
|
|
|
|
// This resource type doesn't have a raw format; save cooked instead
|
|
else
|
|
{
|
|
bool CookSuccess = Cook();
|
|
|
|
if (!CookSuccess)
|
|
{
|
|
errorf("Failed to save resource: %s.%s", *Name(), *CookedExtension().ToString());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Resource has been saved; now make sure metadata, dependencies, and packages are all up to date
|
|
SetFlag(EResEntryFlag::HasBeenModified);
|
|
SaveMetadata();
|
|
UpdateDependencies();
|
|
|
|
if (!SkipCacheSave)
|
|
{
|
|
mpStore->ConditionalSaveStore();
|
|
}
|
|
|
|
// Flag dirty any packages that contain this resource.
|
|
if (FlagForRecook)
|
|
{
|
|
for (uint32 iPkg = 0; iPkg < mpStore->Project()->NumPackages(); iPkg++)
|
|
{
|
|
CPackage *pPkg = mpStore->Project()->PackageByIndex(iPkg);
|
|
|
|
if (!pPkg->NeedsRecook() && pPkg->ContainsAsset(ID()))
|
|
pPkg->MarkDirty();
|
|
}
|
|
}
|
|
|
|
if (ShouldCollectGarbage)
|
|
mpStore->DestroyUnreferencedResources();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CResourceEntry::Cook()
|
|
{
|
|
Load();
|
|
if (!mpResource) return false;
|
|
|
|
TString Path = CookedAssetPath();
|
|
TString Dir = Path.GetFileDirectory();
|
|
FileUtil::MakeDirectory(Dir);
|
|
|
|
// Attempt to open output cooked file
|
|
CFileOutStream File(Path, EEndian::BigEndian);
|
|
if (!File.IsValid())
|
|
{
|
|
errorf("Failed to open cooked file for writing: %s", *Path);
|
|
return false;
|
|
}
|
|
|
|
bool Success = CResourceCooker::CookResource(this, File);
|
|
|
|
if (Success)
|
|
{
|
|
ClearFlag(EResEntryFlag::NeedsRecook);
|
|
SetFlag(EResEntryFlag::HasBeenModified);
|
|
SaveMetadata();
|
|
}
|
|
|
|
return Success;
|
|
}
|
|
|
|
CResource* CResourceEntry::Load()
|
|
{
|
|
// If the asset is already loaded then just return it immediately
|
|
if (mpResource) return mpResource;
|
|
|
|
// Always try to load raw version as the raw version contains extra editor-only data.
|
|
// If there is no raw version (which will be the case for resource types that don't
|
|
// support serialization yet) then load the cooked version as a backup.
|
|
if (HasRawVersion())
|
|
{
|
|
mpResource = CResourceFactory::CreateResource(this);
|
|
|
|
if (mpResource)
|
|
{
|
|
// Set gpResourceStore to ensure the correct resource store is accessed by loader functions
|
|
CResourceStore *pOldStore = gpResourceStore;
|
|
gpResourceStore = mpStore;
|
|
|
|
CXMLReader Reader(RawAssetPath());
|
|
|
|
if (!Reader.IsValid())
|
|
{
|
|
errorf("Failed to load raw resource; falling back on cooked. Raw path: %s", *RawAssetPath());
|
|
delete mpResource;
|
|
mpResource = nullptr;
|
|
}
|
|
|
|
else
|
|
{
|
|
mpResource->Serialize(Reader);
|
|
mpStore->TrackLoadedResource(this);
|
|
gpResourceStore = pOldStore;
|
|
}
|
|
}
|
|
|
|
if (mpResource)
|
|
return mpResource;
|
|
}
|
|
|
|
ASSERT(!mpResource);
|
|
if (HasCookedVersion())
|
|
{
|
|
CFileInStream File(CookedAssetPath(), EEndian::BigEndian);
|
|
|
|
if (!File.IsValid())
|
|
{
|
|
errorf("Failed to open cooked resource: %s", *CookedAssetPath(true));
|
|
return nullptr;
|
|
}
|
|
|
|
return LoadCooked(File);
|
|
}
|
|
|
|
else
|
|
{
|
|
errorf("Couldn't locate resource: %s", *CookedAssetPath(true));
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
CResource* CResourceEntry::LoadCooked(IInputStream& rInput)
|
|
{
|
|
// Overload to allow for load from an arbitrary input stream.
|
|
if (mpResource) return mpResource;
|
|
if (!rInput.IsValid()) return nullptr;
|
|
|
|
// Set gpResourceStore to ensure the correct resource store is accessed by loader functions
|
|
CResourceStore *pOldStore = gpResourceStore;
|
|
gpResourceStore = mpStore;
|
|
|
|
mpResource = CResourceFactory::LoadCookedResource(this, rInput);
|
|
if (mpResource)
|
|
mpStore->TrackLoadedResource(this);
|
|
|
|
gpResourceStore = pOldStore;
|
|
return mpResource;
|
|
}
|
|
|
|
bool CResourceEntry::Unload()
|
|
{
|
|
ASSERT(mpResource != nullptr);
|
|
ASSERT(!mpResource->IsReferenced());
|
|
delete mpResource;
|
|
mpResource = nullptr;
|
|
return true;
|
|
}
|
|
|
|
bool CResourceEntry::CanMoveTo(const TString& rkDir, const TString& rkName)
|
|
{
|
|
// Validate that the path/name are valid
|
|
if (!mpStore->IsValidResourcePath(rkDir, rkName)) return false;
|
|
|
|
// We need to validate the path isn't taken already - either the directory doesn't exist, or doesn't have a resource by this name
|
|
CVirtualDirectory *pDir = mpStore->GetVirtualDirectory(rkDir, false);
|
|
|
|
if (pDir)
|
|
{
|
|
if (pDir == mpDirectory && rkName == mName)
|
|
return false;
|
|
|
|
if (pDir->FindChildResource(rkName, ResourceType()))
|
|
return false;
|
|
}
|
|
|
|
// All checks are true
|
|
return true;
|
|
}
|
|
|
|
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
|
|
CVirtualDirectory *pOldDir = mpDirectory;
|
|
TString OldName = mName;
|
|
TString OldCookedPath = CookedAssetPath();
|
|
TString OldRawPath = RawAssetPath();
|
|
TString OldMetaPath = MetadataFilePath();
|
|
|
|
// Set new directory and name
|
|
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
|
|
ASSERT(pNewDir->FindChildResource(rkName, ResourceType()) == nullptr); // this check should be guaranteed to pass due to CanMoveTo() having already checked it
|
|
|
|
mpDirectory = pNewDir;
|
|
mName = rkName;
|
|
TString NewCookedPath = CookedAssetPath();
|
|
TString NewRawPath = RawAssetPath();
|
|
TString NewMetaPath = MetadataFilePath();
|
|
|
|
debugf("MOVING RESOURCE: %s --> %s",
|
|
*FileUtil::MakeRelative(OldCookedPath, mpStore->ResourcesDir()),
|
|
*FileUtil::MakeRelative(NewCookedPath, mpStore->ResourcesDir())
|
|
);
|
|
|
|
// If the old/new paths are the same then we should have already exited as CanMoveTo() should have returned false
|
|
ASSERT(OldCookedPath != NewCookedPath && OldRawPath != NewRawPath && OldMetaPath != NewMetaPath);
|
|
|
|
// The cooked/raw asset paths should not exist right now!!!
|
|
bool FSMoveSuccess = false;
|
|
TString MoveFailReason;
|
|
|
|
if (!FileUtil::Exists(NewCookedPath) && !FileUtil::Exists(NewRawPath) && !FileUtil::Exists(NewMetaPath))
|
|
{
|
|
FSMoveSuccess = true;
|
|
|
|
// Move raw file to new location
|
|
if (FileUtil::Exists(OldRawPath))
|
|
{
|
|
FSMoveSuccess = FileUtil::MoveFile(OldRawPath, NewRawPath);
|
|
|
|
if (!FSMoveSuccess)
|
|
MoveFailReason = TString::Format("Failed to move raw file to new destination (%s --> %s)", *OldRawPath, *NewRawPath);
|
|
}
|
|
|
|
// Move cooked file to new location
|
|
if (FSMoveSuccess && FileUtil::Exists(OldCookedPath))
|
|
{
|
|
FSMoveSuccess = FileUtil::MoveFile(OldCookedPath, NewCookedPath);
|
|
|
|
if (!FSMoveSuccess)
|
|
{
|
|
MoveFailReason = TString::Format("Failed to move cooked file to new destination (%s --> %s)", *OldCookedPath, *NewCookedPath);
|
|
}
|
|
}
|
|
|
|
// Move metadata file to new location
|
|
if (FSMoveSuccess)
|
|
{
|
|
if (FileUtil::Exists(OldMetaPath))
|
|
{
|
|
FSMoveSuccess = FileUtil::MoveFile(OldMetaPath, NewMetaPath);
|
|
|
|
if (!FSMoveSuccess)
|
|
{
|
|
MoveFailReason = TString::Format("Failed to move metadata file to new destination (%s --> %s)", *OldMetaPath, *NewMetaPath);
|
|
}
|
|
}
|
|
|
|
else
|
|
mMetadataDirty = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool HasCooked = FileUtil::Exists(NewCookedPath);
|
|
bool HasRaw = FileUtil::Exists(NewRawPath);
|
|
|
|
TString BadFileType = HasCooked ? "cooked" : (HasRaw ? "raw" : "metadata");
|
|
TString BadFilePath = HasCooked ? NewCookedPath : (HasRaw ? NewRawPath : NewMetaPath);
|
|
|
|
MoveFailReason = TString::Format("File already exists at %s asset destination (%s)", *BadFileType, *BadFilePath);
|
|
}
|
|
|
|
// If we succeeded, finish the move
|
|
if (FSMoveSuccess)
|
|
{
|
|
if (mpDirectory != pOldDir && pOldDir != nullptr)
|
|
{
|
|
FSMoveSuccess = pOldDir->RemoveChildResource(this);
|
|
ASSERT(FSMoveSuccess == true); // this shouldn't be able to fail
|
|
mpDirectory->AddChild("", this);
|
|
SetFlagEnabled(EResEntryFlag::AutoResDir, IsAutoGenDir);
|
|
}
|
|
|
|
if (mName != OldName)
|
|
{
|
|
SetFlagEnabled(EResEntryFlag::AutoResName, IsAutoGenName);
|
|
}
|
|
|
|
mpStore->SetCacheDirty();
|
|
mCachedUppercaseName = rkName.ToUpper();
|
|
SaveMetadata();
|
|
return true;
|
|
}
|
|
|
|
// Otherwise, revert changes and let the caller know the move failed
|
|
else
|
|
{
|
|
errorf("MOVE FAILED: %s", *MoveFailReason);
|
|
mpDirectory = pOldDir;
|
|
mName = OldName;
|
|
|
|
if (!DirAlreadyExisted)
|
|
{
|
|
mpStore->ConditionalDeleteDirectory(pNewDir, true);
|
|
}
|
|
|
|
if (FileUtil::Exists(NewRawPath))
|
|
FileUtil::MoveFile(NewRawPath, OldRawPath);
|
|
|
|
if (FileUtil::Exists(NewCookedPath))
|
|
FileUtil::MoveFile(NewCookedPath, OldCookedPath);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool CResourceEntry::Move(const TString& rkDir, bool IsAutoGenDir /*= false*/)
|
|
{
|
|
return MoveAndRename(rkDir, mName, IsAutoGenDir, false);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
EGame CResourceEntry::Game() const
|
|
{
|
|
return mpStore ? mpStore->Game() : EGame::Invalid;
|
|
}
|
|
|
|
void CResourceEntry::SetFlag(EResEntryFlag Flag)
|
|
{
|
|
if (!HasFlag(Flag))
|
|
{
|
|
mFlags.SetFlag(Flag);
|
|
mMetadataDirty = true;
|
|
}
|
|
}
|
|
|
|
void CResourceEntry::ClearFlag(EResEntryFlag Flag)
|
|
{
|
|
if (HasFlag(Flag))
|
|
{
|
|
mFlags.ClearFlag(Flag);
|
|
mMetadataDirty = true;
|
|
}
|
|
}
|