You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
10 KiB
C++
327 lines
10 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include "ProjectSettingsContainer.h"
|
|
|
|
#include <AzCore/IO/SystemFile.h>
|
|
#include <AzCore/JSON/prettywriter.h>
|
|
#include <AzCore/JSON/stringbuffer.h>
|
|
#include <AzCore/XML/rapidxml_print.h>
|
|
|
|
#include <CryCommon/platform.h>
|
|
#include <Util/FileUtil.h>
|
|
|
|
namespace ProjectSettingsTool
|
|
{
|
|
using namespace AZ;
|
|
using StringOutcome = Outcome<void, AZStd::string>;
|
|
using XmlDocument = rapidxml::xml_document<char>;
|
|
using XmlNode = rapidxml::xml_node<char>;
|
|
|
|
const int xmlFlags = rapidxml::parse_doctype_node | rapidxml::parse_declaration_node | rapidxml::parse_no_data_nodes;
|
|
|
|
SettingsError::SettingsError(const AZStd::string& error, const AZStd::string& reason)
|
|
{
|
|
m_error = error;
|
|
m_reason = reason;
|
|
}
|
|
|
|
StringOutcome WriteConfigFile(const AZStd::string& fileName, const AZStd::string& fileContents)
|
|
{
|
|
using AZ::IO::SystemFile;
|
|
const char* filePath = fileName.c_str();
|
|
|
|
// Attempt to make file writable or check it out in source control
|
|
if (CFileUtil::OverwriteFile(filePath))
|
|
{
|
|
if (!CFileUtil::CreateDirectory(fileName.substr(0, fileName.find_last_of('/')).data()))
|
|
{
|
|
return AZ::Failure(AZStd::string::format("Could not create the directory for file \"%s\".", filePath));
|
|
}
|
|
|
|
SystemFile settingsFile;
|
|
if (!settingsFile.Open(filePath, SystemFile::SF_OPEN_WRITE_ONLY | SystemFile::SF_OPEN_CREATE))
|
|
{
|
|
return AZ::Failure(AZStd::string::format("Failed to open settings file %s for write.", filePath));
|
|
}
|
|
|
|
if (settingsFile.Write(fileContents.c_str(), fileContents.size()) != fileContents.size())
|
|
{
|
|
return AZ::Failure(AZStd::string::format("Failed to write to file %s.", filePath));
|
|
}
|
|
|
|
|
|
settingsFile.Close();
|
|
return AZ::Success();
|
|
}
|
|
|
|
return AZ::Failure(AZStd::string::format("Could not check out or make file writable: \"%s\".", filePath));
|
|
}
|
|
|
|
StringOutcome ReadConfigFile(const AZStd::string& fileName, AZStd::string& fileContents)
|
|
{
|
|
using AZ::IO::SystemFile;
|
|
const char* filePath = fileName.c_str();
|
|
|
|
if (!SystemFile::Exists(filePath))
|
|
{
|
|
return AZ::Failure(AZStd::string::format("%s file doesn't exist.", filePath));
|
|
}
|
|
|
|
SystemFile settingsFile;
|
|
if (!settingsFile.Open(filePath, SystemFile::SF_OPEN_READ_ONLY))
|
|
{
|
|
return AZ::Failure(AZStd::string::format("Failed to open settings file %s.", filePath));
|
|
}
|
|
|
|
fileContents = AZStd::string(settingsFile.Length(), '\0');
|
|
settingsFile.Read(fileContents.size(), fileContents.data());
|
|
|
|
settingsFile.Close();
|
|
return AZ::Success();
|
|
}
|
|
|
|
|
|
ProjectSettingsContainer::ProjectSettingsContainer(const AZStd::string& projectJsonFileName, PlistInitVector& plistPaths)
|
|
: m_projectJson(JsonSettings{ projectJsonFileName, "", AZStd::make_unique<rapidjson::Document>() })
|
|
{
|
|
LoadProjectJsonData();
|
|
|
|
for (PlatformAndPath& plistInfo : plistPaths)
|
|
{
|
|
m_pListsMap.insert(AZStd::pair<PlatformId, PlistSettings>(
|
|
plistInfo.first,
|
|
PlistSettings{ plistInfo.second, "", AZStd::make_unique<XmlDocument>() }));
|
|
|
|
// We will have to find it then because unique_ptrs are not copy constructible
|
|
// And a move constructor would copy large strings
|
|
LoadPlist(m_pListsMap.find(plistInfo.first)->second);
|
|
}
|
|
}
|
|
|
|
ProjectSettingsContainer::~ProjectSettingsContainer()
|
|
{
|
|
}
|
|
|
|
ProjectSettingsContainer::PlistSettings* ProjectSettingsContainer::GetPlistSettingsForPlatform(const Platform& plat)
|
|
{
|
|
PlistSettings* result = nullptr;
|
|
|
|
if (plat.m_type == PlatformDataType::Plist)
|
|
{
|
|
auto iter = m_pListsMap.find(plat.m_id);
|
|
|
|
if (iter != m_pListsMap.end())
|
|
{
|
|
result = &iter->second;
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "Failed to find pList for platform.");
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool ProjectSettingsContainer::IsPlistPlatform(const Platform& plat)
|
|
{
|
|
if (plat.m_type == PlatformDataType::Plist)
|
|
{
|
|
auto iter = m_pListsMap.find(plat.m_id);
|
|
|
|
if (iter != m_pListsMap.end())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
AZ::Outcome<void, SettingsError> ProjectSettingsContainer::GetError()
|
|
{
|
|
if (!m_errors.empty())
|
|
{
|
|
SettingsError error = m_errors.front();
|
|
m_errors.pop();
|
|
|
|
return AZ::Failure(error);
|
|
}
|
|
return AZ::Success();
|
|
}
|
|
|
|
void ProjectSettingsContainer::SavePlatformData(const Platform& plat)
|
|
{
|
|
PlistSettings* plistSettings = GetPlistSettingsForPlatform(plat);
|
|
if (plistSettings != nullptr)
|
|
{
|
|
SavePlist(*plistSettings);
|
|
}
|
|
else
|
|
{
|
|
SaveProjectJsonData();
|
|
}
|
|
}
|
|
|
|
void ProjectSettingsContainer::SaveProjectJsonData()
|
|
{
|
|
// Needed to write a document out to a string
|
|
rapidjson::StringBuffer jsonDataBuffer;
|
|
// Use pretty writer so it can be read easier
|
|
rapidjson::PrettyWriter<rapidjson::StringBuffer> jsonDatawriter(jsonDataBuffer);
|
|
|
|
m_projectJson.m_document->Accept(jsonDatawriter);
|
|
const AZStd::string jsonDataString = jsonDataBuffer.GetString();
|
|
|
|
StringOutcome outcome = WriteConfigFile(m_projectJson.m_path, jsonDataString);
|
|
|
|
if (!outcome.IsSuccess())
|
|
{
|
|
m_errors.push(SettingsError("Failed to save project.json", outcome.GetError()));
|
|
}
|
|
}
|
|
|
|
void ProjectSettingsContainer::ReloadProjectJsonData()
|
|
{
|
|
m_projectJson.m_document.reset(new rapidjson::Document);
|
|
LoadProjectJsonData();
|
|
}
|
|
|
|
void ProjectSettingsContainer::SavePlistsData()
|
|
{
|
|
for (AZStd::pair<PlatformId, PlistSettings>& plist : m_pListsMap)
|
|
{
|
|
LoadPlist(plist.second);
|
|
}
|
|
}
|
|
|
|
void ProjectSettingsContainer::SavePlistData(const Platform& plat)
|
|
{
|
|
PlistSettings* settings = GetPlistSettingsForPlatform(plat);
|
|
if (settings != nullptr)
|
|
{
|
|
SavePlist(*settings);
|
|
}
|
|
}
|
|
|
|
void ProjectSettingsContainer::ReloadPlistData()
|
|
{
|
|
for (AZStd::pair<PlatformId, PlistSettings>& plist : m_pListsMap)
|
|
{
|
|
PlistSettings& plistSettings = plist.second;
|
|
|
|
plistSettings.m_document.reset(new XmlDocument());
|
|
LoadPlist(plistSettings);
|
|
}
|
|
}
|
|
|
|
rapidjson::Document& ProjectSettingsContainer::GetProjectJsonDocument()
|
|
{
|
|
return *m_projectJson.m_document.get();
|
|
}
|
|
|
|
AZ::Outcome<rapidjson::Value*, void> ProjectSettingsContainer::GetProjectJsonValue(const char* key)
|
|
{
|
|
// Try to find member
|
|
rapidjson::Document& settings = *m_projectJson.m_document;
|
|
rapidjson::Value::MemberIterator memberIterator = settings.FindMember(key);
|
|
if (memberIterator != settings.MemberEnd())
|
|
{
|
|
return AZ::Success(&memberIterator->value);
|
|
}
|
|
else
|
|
{
|
|
settings.AddMember(rapidjson::Value(key, settings.GetAllocator()),
|
|
rapidjson::Value(rapidjson::kNullType), settings.GetAllocator());
|
|
return AZ::Success(&settings.FindMember(key)->value);
|
|
}
|
|
}
|
|
|
|
AZStd::unique_ptr<PlistDictionary> ProjectSettingsContainer::GetPlistDictionary(const Platform& plat)
|
|
{
|
|
if (plat.m_type == PlatformDataType::Plist)
|
|
{
|
|
PlistSettings* settings = GetPlistSettingsForPlatform(plat);
|
|
if (settings != nullptr)
|
|
{
|
|
|
|
if (PlistDictionary::ContainsValidDict(settings->m_document.get()))
|
|
{
|
|
return AZStd::make_unique<PlistDictionary>(settings->m_document.get());
|
|
}
|
|
else
|
|
{
|
|
//TODO: Query user if they would like to remake a valid plist then do it
|
|
AZStd::string platformName;
|
|
switch (plat.m_id)
|
|
{
|
|
case PlatformId::Ios:
|
|
platformName = "iOS";
|
|
break;
|
|
default:
|
|
platformName = "unknown";
|
|
break;
|
|
}
|
|
AZ_Assert(false, "%s pList is in invalid state.", platformName.c_str());
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
AZ_Assert(false, "This platform does not use pLists to store data.");
|
|
return nullptr;
|
|
}
|
|
|
|
rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>& ProjectSettingsContainer::GetProjectJsonAllocator()
|
|
{
|
|
return m_projectJson.m_document->GetAllocator();
|
|
}
|
|
|
|
const char* ProjectSettingsContainer::GetFailedLoadingPlistText()
|
|
{
|
|
return "Failed to load info.plist";
|
|
}
|
|
|
|
void ProjectSettingsContainer::LoadProjectJsonData()
|
|
{
|
|
StringOutcome outcome = ReadConfigFile(m_projectJson.m_path, m_projectJson.m_rawData);
|
|
if (!outcome.IsSuccess())
|
|
{
|
|
m_errors.push(SettingsError("Failed to load project.json", outcome.GetError()));
|
|
}
|
|
|
|
m_projectJson.m_document->Parse(m_projectJson.m_rawData.c_str());
|
|
}
|
|
|
|
// Loads info.plist for iOS from disk
|
|
void ProjectSettingsContainer::LoadPlist(PlistSettings& plistSettings)
|
|
{
|
|
StringOutcome outcome = ReadConfigFile(plistSettings.m_path, plistSettings.m_rawData);
|
|
if (!outcome.IsSuccess())
|
|
{
|
|
m_errors.push(SettingsError(GetFailedLoadingPlistText(), outcome.GetError()));
|
|
}
|
|
|
|
plistSettings.m_document->parse<xmlFlags>(plistSettings.m_rawData.data());
|
|
}
|
|
|
|
void ProjectSettingsContainer::SavePlist(PlistSettings& plistSettings)
|
|
{
|
|
// Needed to write a document out to a string
|
|
AZStd::string xmlDocString;
|
|
rapidxml::print(std::back_inserter(xmlDocString), *plistSettings.m_document);
|
|
|
|
StringOutcome outcome = WriteConfigFile(plistSettings.m_path, xmlDocString);
|
|
|
|
if (!outcome.IsSuccess())
|
|
{
|
|
m_errors.push(SettingsError("Failed to save info.pList", outcome.GetError()));
|
|
}
|
|
}
|
|
} // namespace ProjectSettingsTool
|