AudioControlsWriter.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AudioControlsWriter.h>
  9. #include <AzCore/IO/ByteContainerStream.h>
  10. #include <AzCore/IO/TextStreamWriters.h>
  11. #include <AzCore/std/string/conversions.h>
  12. #include <AzCore/StringFunc/StringFunc.h>
  13. #include <AzCore/Utils/Utils.h>
  14. #include <AzCore/XML/rapidxml_print.h>
  15. #include <ACEEnums.h>
  16. #include <ATLControlsModel.h>
  17. #include <IAudioSystem.h>
  18. #include <IAudioSystemControl.h>
  19. #include <IAudioSystemEditor.h>
  20. #include <IEditor.h>
  21. #include <Include/IFileUtil.h>
  22. #include <QModelIndex>
  23. #include <QStandardItemModel>
  24. #include <QFileInfo>
  25. namespace AudioControls
  26. {
  27. namespace WriterStrings
  28. {
  29. static constexpr const char* LevelsSubFolder = "levels";
  30. static constexpr const char* LibraryExtension = ".xml";
  31. } // namespace WriterStrings
  32. //-------------------------------------------------------------------------------------------//
  33. AZStd::string_view TypeToTag(EACEControlType type)
  34. {
  35. switch (type)
  36. {
  37. case eACET_RTPC:
  38. return Audio::ATLXmlTags::ATLRtpcTag;
  39. case eACET_TRIGGER:
  40. return Audio::ATLXmlTags::ATLTriggerTag;
  41. case eACET_SWITCH:
  42. return Audio::ATLXmlTags::ATLSwitchTag;
  43. case eACET_SWITCH_STATE:
  44. return Audio::ATLXmlTags::ATLSwitchStateTag;
  45. case eACET_PRELOAD:
  46. return Audio::ATLXmlTags::ATLPreloadRequestTag;
  47. case eACET_ENVIRONMENT:
  48. return Audio::ATLXmlTags::ATLEnvironmentTag;
  49. }
  50. return "";
  51. }
  52. //-------------------------------------------------------------------------------------------//
  53. CAudioControlsWriter::CAudioControlsWriter(CATLControlsModel* atlModel, QStandardItemModel* layoutModel, IAudioSystemEditor* audioSystemImpl, FilepathSet& previousLibraryPaths)
  54. : m_atlModel(atlModel)
  55. , m_layoutModel(layoutModel)
  56. , m_audioSystemImpl(audioSystemImpl)
  57. {
  58. if (m_atlModel && m_layoutModel && m_audioSystemImpl)
  59. {
  60. m_layoutModel->blockSignals(true);
  61. int i = 0;
  62. QModelIndex index = m_layoutModel->index(i, 0);
  63. while (index.isValid())
  64. {
  65. WriteLibrary(index.data(Qt::DisplayRole).toString().toUtf8().data(), index);
  66. index = index.sibling(++i, 0);
  67. }
  68. auto fileIO = AZ::IO::FileIOBase::GetInstance();
  69. AZStd::for_each(
  70. m_foundLibraryPaths.begin(), m_foundLibraryPaths.end(),
  71. [fileIO](AZStd::string& libraryPath) -> void
  72. {
  73. if (auto newPathOpt = fileIO->ConvertToAlias(AZ::IO::PathView{ libraryPath });
  74. newPathOpt.has_value())
  75. {
  76. libraryPath = newPathOpt.value().Native();
  77. }
  78. AZStd::to_lower(libraryPath.begin(), libraryPath.end());
  79. });
  80. // Delete libraries that don't exist anymore from disk
  81. FilepathSet librariesToDelete;
  82. AZStd::set_difference(
  83. previousLibraryPaths.begin(), previousLibraryPaths.end(),
  84. m_foundLibraryPaths.begin(), m_foundLibraryPaths.end(),
  85. AZStd::inserter(librariesToDelete, librariesToDelete.begin())
  86. );
  87. for (auto it = librariesToDelete.begin(); it != librariesToDelete.end(); ++it)
  88. {
  89. auto newPathOpt = fileIO->ResolvePath(AZ::IO::PathView{ *it });
  90. DeleteLibraryFile(newPathOpt.value().Native());
  91. }
  92. previousLibraryPaths = m_foundLibraryPaths;
  93. m_layoutModel->blockSignals(false);
  94. }
  95. }
  96. //-------------------------------------------------------------------------------------------//
  97. void CAudioControlsWriter::WriteLibrary(const AZStd::string_view libraryName, QModelIndex root)
  98. {
  99. const char* controlsPath = AZ::Interface<Audio::IAudioSystem>::Get()->GetControlsPath();
  100. if (root.isValid() && controlsPath)
  101. {
  102. TLibraryStorage library;
  103. int i = 0;
  104. QModelIndex child = root.model()->index(i, 0, root);
  105. while (child.isValid())
  106. {
  107. WriteItem(child, "", library, root.data(eDR_MODIFIED).toBool());
  108. child = root.model()->index(++i, 0, root);
  109. }
  110. for (auto& libraryPair : library)
  111. {
  112. AZ::IO::FixedMaxPath libraryPath{ controlsPath };
  113. const AZStd::string& scope = libraryPair.first;
  114. if (scope.empty())
  115. {
  116. // no scope, file at the root level
  117. libraryPath /= libraryName;
  118. libraryPath.ReplaceExtension(WriterStrings::LibraryExtension);
  119. }
  120. else
  121. {
  122. // with scope, inside level folder
  123. libraryPath /= AZ::IO::FixedMaxPath{ WriterStrings::LevelsSubFolder } / scope / libraryName;
  124. libraryPath.ReplaceExtension(WriterStrings::LibraryExtension);
  125. }
  126. AZ::IO::FixedMaxPath fullFilePath = AZ::Utils::GetProjectPath();
  127. fullFilePath /= libraryPath;
  128. m_foundLibraryPaths.insert(fullFilePath.c_str());
  129. const SLibraryScope& libScope = libraryPair.second;
  130. if (libScope.m_isDirty)
  131. {
  132. XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator);
  133. AZ::rapidxml::xml_node<char>* fileNode =
  134. xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(Audio::ATLXmlTags::RootNodeTag));
  135. AZ::rapidxml::xml_attribute<char>* nameAttr = xmlAlloc.allocate_attribute(
  136. xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(libraryName.data()));
  137. fileNode->append_attribute(nameAttr);
  138. for (int ii = 0; ii < eACET_NUM_TYPES; ++ii)
  139. {
  140. if (libScope.m_nodes[ii] && libScope.m_nodes[ii]->first_node() != nullptr)
  141. {
  142. fileNode->append_node(libScope.m_nodes[ii]);
  143. }
  144. }
  145. if (auto fileInfo = QFileInfo(fullFilePath.c_str());
  146. fileInfo.exists())
  147. {
  148. if (!fileInfo.isWritable())
  149. {
  150. // file exists and is read-only
  151. CheckOutFile(fullFilePath.Native());
  152. }
  153. [[maybe_unused]] bool writeOk = WriteXmlToFile(fullFilePath.Native(), fileNode);
  154. }
  155. else
  156. {
  157. // since it's a new file, save the file first, CheckOutFile will add it
  158. [[maybe_unused]] bool writeOk = WriteXmlToFile(fullFilePath.Native(), fileNode);
  159. CheckOutFile(fullFilePath.Native());
  160. }
  161. }
  162. }
  163. }
  164. }
  165. //-------------------------------------------------------------------------------------------//
  166. void CAudioControlsWriter::WriteItem(QModelIndex index, const AZStd::string& path, TLibraryStorage& library, bool isParentModified)
  167. {
  168. if (index.isValid())
  169. {
  170. if (index.data(eDR_TYPE) == eIT_FOLDER)
  171. {
  172. int i = 0;
  173. QModelIndex child = index.model()->index(i, 0, index);
  174. while (child.isValid())
  175. {
  176. AZStd::string newPath = path.empty() ? "" : path + "/";
  177. newPath += index.data(Qt::DisplayRole).toString().toUtf8().data();
  178. WriteItem(child, newPath, library, index.data(eDR_MODIFIED).toBool() || isParentModified);
  179. child = index.model()->index(++i, 0, index);
  180. }
  181. QStandardItem* item = m_layoutModel->itemFromIndex(index);
  182. if (item)
  183. {
  184. item->setData(false, eDR_MODIFIED);
  185. }
  186. }
  187. else
  188. {
  189. CATLControl* control = m_atlModel->GetControlByID(index.data(eDR_ID).toUInt());
  190. if (control)
  191. {
  192. SLibraryScope& scope = library[control->GetScope()];
  193. if (IsItemModified(index) || isParentModified)
  194. {
  195. scope.m_isDirty = true;
  196. QStandardItem* item = m_layoutModel->itemFromIndex(index);
  197. if (item)
  198. {
  199. item->setData(false, eDR_MODIFIED);
  200. }
  201. }
  202. WriteControlToXml(scope.m_nodes[control->GetType()], control, path);
  203. }
  204. }
  205. }
  206. }
  207. //-------------------------------------------------------------------------------------------//
  208. bool CAudioControlsWriter::IsItemModified(QModelIndex index)
  209. {
  210. if (index.data(eDR_MODIFIED).toBool() == true)
  211. {
  212. return true;
  213. }
  214. int i = 0;
  215. QModelIndex child = index.model()->index(i, 0, index);
  216. while (child.isValid())
  217. {
  218. if (IsItemModified(child))
  219. {
  220. return true;
  221. }
  222. child = index.model()->index(++i, 0, index);
  223. }
  224. return false;
  225. }
  226. //-------------------------------------------------------------------------------------------//
  227. bool CAudioControlsWriter::WriteXmlToFile(const AZStd::string_view filepath, AZ::rapidxml::xml_node<char>* rootNode)
  228. {
  229. if (!rootNode)
  230. {
  231. return false;
  232. }
  233. using namespace AZ::IO;
  234. AZStd::string docString;
  235. ByteContainerStream stringStream(&docString);
  236. AZ::rapidxml::xml_document<char> xmlDoc;
  237. xmlDoc.append_node(rootNode);
  238. RapidXMLStreamWriter streamWriter(&stringStream);
  239. AZ::rapidxml::print(streamWriter.Iterator(), xmlDoc);
  240. streamWriter.FlushCache();
  241. constexpr int openMode =
  242. (SystemFile::SF_OPEN_WRITE_ONLY | SystemFile::SF_OPEN_CREATE | SystemFile::SF_OPEN_CREATE_PATH);
  243. if (SystemFile fileOut;
  244. fileOut.Open(filepath.data(), openMode))
  245. {
  246. auto bytesWritten = fileOut.Write(docString.data(), docString.size());
  247. return (bytesWritten == docString.size());
  248. }
  249. return false;
  250. }
  251. //-------------------------------------------------------------------------------------------//
  252. void CAudioControlsWriter::WriteControlToXml(AZ::rapidxml::xml_node<char>* node, CATLControl* control, const AZStd::string_view path)
  253. {
  254. if (!node || !control)
  255. {
  256. return;
  257. }
  258. XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator);
  259. const EACEControlType type = control->GetType();
  260. AZStd::string_view typeName = TypeToTag(type);
  261. AZ::rapidxml::xml_node<char>* childNode =
  262. xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(typeName.data()));
  263. AZ::rapidxml::xml_attribute<char>* nameAttr = xmlAlloc.allocate_attribute(
  264. xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(control->GetName().c_str()));
  265. childNode->append_attribute(nameAttr);
  266. if (!path.empty())
  267. {
  268. AZ::rapidxml::xml_attribute<char>* pathAttr = xmlAlloc.allocate_attribute(
  269. xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLPathAttribute), xmlAlloc.allocate_string(path.data()));
  270. childNode->append_attribute(pathAttr);
  271. }
  272. if (type == eACET_SWITCH)
  273. {
  274. const size_t size = control->ChildCount();
  275. for (size_t i = 0; i < size; ++i)
  276. {
  277. WriteControlToXml(childNode, control->GetChild(i), "");
  278. }
  279. }
  280. else if (type == eACET_PRELOAD)
  281. {
  282. if (control->IsAutoLoad())
  283. {
  284. AZ::rapidxml::xml_attribute<char>* loadAttr = xmlAlloc.allocate_attribute(
  285. xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLTypeAttribute),
  286. xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLDataLoadType));
  287. childNode->append_attribute(loadAttr);
  288. }
  289. // New Preloads XML...
  290. WriteConnectionsToXml(childNode, control);
  291. }
  292. else
  293. {
  294. WriteConnectionsToXml(childNode, control);
  295. }
  296. node->append_node(childNode);
  297. }
  298. //-------------------------------------------------------------------------------------------//
  299. void CAudioControlsWriter::WriteConnectionsToXml(AZ::rapidxml::xml_node<char>* node, CATLControl* control)
  300. {
  301. if (node && control && m_audioSystemImpl)
  302. {
  303. for (auto& connectionNode : control->m_connectionNodes)
  304. {
  305. if (!connectionNode.m_isValid)
  306. {
  307. XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator);
  308. node->append_node(xmlAlloc.clone_node(connectionNode.m_xmlNode));
  309. }
  310. }
  311. const size_t size = control->ConnectionCount();
  312. for (size_t i = 0; i < size; ++i)
  313. {
  314. if (TConnectionPtr connection = control->GetConnectionAt(i);
  315. connection != nullptr)
  316. {
  317. if (auto childNode = m_audioSystemImpl->CreateXMLNodeFromConnection(connection, control->GetType());
  318. childNode != nullptr)
  319. {
  320. node->append_node(childNode);
  321. control->m_connectionNodes.emplace_back(childNode, true);
  322. }
  323. }
  324. }
  325. }
  326. }
  327. //-------------------------------------------------------------------------------------------//
  328. void CAudioControlsWriter::CheckOutFile(const AZStd::string_view filepath)
  329. {
  330. IEditor* editor = GetIEditor();
  331. IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr;
  332. if (fileUtil)
  333. {
  334. fileUtil->CheckoutFile(AZ::IO::FixedMaxPath{ filepath }.c_str(), nullptr);
  335. }
  336. }
  337. //-------------------------------------------------------------------------------------------//
  338. void CAudioControlsWriter::DeleteLibraryFile(const AZStd::string_view filepath)
  339. {
  340. IEditor* editor = GetIEditor();
  341. IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr;
  342. if (fileUtil)
  343. {
  344. fileUtil->DeleteFromSourceControl(AZ::IO::FixedMaxPath{ filepath }.c_str(), nullptr);
  345. }
  346. }
  347. } // namespace AudioControls