AudioControlsWriter.cpp 15 KB

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