2 * Copyright (C) 2021 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "PortManager.h"
12 #include "games/controllers/Controller.h"
13 #include "games/controllers/types/ControllerHub.h"
14 #include "games/controllers/types/ControllerNode.h"
15 #include "games/ports/types/PortNode.h"
16 #include "utils/FileUtils.h"
17 #include "utils/URIUtils.h"
18 #include "utils/XBMCTinyXML2.h"
19 #include "utils/XMLUtils.h"
20 #include "utils/log.h"
30 constexpr const char* PORT_XML_FILE
= "ports.xml";
31 constexpr const char* XML_ROOT_PORTS
= "ports";
32 constexpr const char* XML_ELM_PORT
= "port";
33 constexpr const char* XML_ELM_CONTROLLER
= "controller";
34 constexpr const char* XML_ATTR_PORT_ID
= "id";
35 constexpr const char* XML_ATTR_PORT_ADDRESS
= "address";
36 constexpr const char* XML_ATTR_PORT_CONNECTED
= "connected";
37 constexpr const char* XML_ATTR_PORT_CONTROLLER
= "controller";
38 constexpr const char* XML_ATTR_CONTROLLER_ID
= "id";
41 CPortManager::CPortManager() = default;
43 CPortManager::~CPortManager() = default;
45 void CPortManager::Initialize(const std::string
& profilePath
)
47 m_xmlPath
= URIUtils::AddFileToFolder(profilePath
, PORT_XML_FILE
);
50 void CPortManager::Deinitialize()
52 // Wait for save tasks
53 for (std::future
<void>& task
: m_saveFutures
)
55 m_saveFutures
.clear();
57 m_controllerTree
.Clear();
61 void CPortManager::SetControllerTree(const CControllerTree
& controllerTree
)
63 m_controllerTree
= controllerTree
;
66 void CPortManager::LoadXML()
68 if (!CFileUtils::Exists(m_xmlPath
))
70 CLog::Log(LOGDEBUG
, "Can't load port config, file doesn't exist: {}",
71 CURL::GetRedacted(m_xmlPath
));
75 CLog::Log(LOGINFO
, "Loading port layout: {}", CURL::GetRedacted(m_xmlPath
));
78 if (!xmlDoc
.LoadFile(m_xmlPath
))
80 CLog::Log(LOGDEBUG
, "Unable to load file: {} at line {}", xmlDoc
.ErrorStr(),
81 xmlDoc
.ErrorLineNum());
85 const auto* pRootElement
= xmlDoc
.RootElement();
86 if (pRootElement
== nullptr || pRootElement
->NoChildren() ||
87 std::strcmp(pRootElement
->Value(), XML_ROOT_PORTS
) != 0)
89 CLog::Log(LOGERROR
, "Can't find root <{}> tag", XML_ROOT_PORTS
);
93 DeserializePorts(pRootElement
, m_controllerTree
.GetPorts());
96 void CPortManager::SaveXMLAsync()
98 PortVec ports
= m_controllerTree
.GetPorts();
100 // Prune any finished save tasks
101 m_saveFutures
.erase(std::remove_if(m_saveFutures
.begin(), m_saveFutures
.end(),
102 [](std::future
<void>& task
) {
103 return task
.wait_for(std::chrono::seconds(0)) ==
104 std::future_status::ready
;
106 m_saveFutures
.end());
109 std::future
<void> task
= std::async(std::launch::async
,
110 [this, ports
= std::move(ports
)]()
113 auto* node
= doc
.NewElement(XML_ROOT_PORTS
);
117 SerializePorts(*node
, ports
);
119 doc
.InsertEndChild(node
);
121 std::lock_guard
<std::mutex
> lock(m_saveMutex
);
122 doc
.SaveFile(m_xmlPath
);
125 m_saveFutures
.emplace_back(std::move(task
));
128 void CPortManager::Clear()
131 m_controllerTree
.Clear();
134 void CPortManager::ConnectController(const std::string
& portAddress
,
136 const std::string
& controllerId
/* = "" */)
138 ConnectController(portAddress
, connected
, controllerId
, m_controllerTree
.GetPorts());
141 bool CPortManager::ConnectController(const std::string
& portAddress
,
143 const std::string
& controllerId
,
146 for (CPortNode
& port
: ports
)
148 if (ConnectController(portAddress
, connected
, controllerId
, port
))
155 bool CPortManager::ConnectController(const std::string
& portAddress
,
157 const std::string
& controllerId
,
161 if (port
.GetAddress() == portAddress
)
163 port
.SetConnected(connected
);
164 if (!controllerId
.empty())
165 port
.SetActiveController(controllerId
);
170 return ConnectController(portAddress
, connected
, controllerId
, port
.GetCompatibleControllers());
173 bool CPortManager::ConnectController(const std::string
& portAddress
,
175 const std::string
& controllerId
,
176 ControllerNodeVec
& controllers
)
178 for (CControllerNode
& controller
: controllers
)
180 if (ConnectController(portAddress
, connected
, controllerId
, controller
))
187 bool CPortManager::ConnectController(const std::string
& portAddress
,
189 const std::string
& controllerId
,
190 CControllerNode
& controller
)
192 for (CPortNode
& childPort
: controller
.GetHub().GetPorts())
194 if (ConnectController(portAddress
, connected
, controllerId
, childPort
))
201 void CPortManager::DeserializePorts(const tinyxml2::XMLElement
* pElement
, PortVec
& ports
)
203 for (const auto* pPort
= pElement
->FirstChildElement(); pPort
!= nullptr;
204 pPort
= pPort
->NextSiblingElement())
206 if (std::strcmp(pPort
->Value(), XML_ELM_PORT
) != 0)
208 CLog::Log(LOGDEBUG
, "Inside <{}> tag: Ignoring <{}> tag", pElement
->Value(), pPort
->Value());
212 std::string portId
= XMLUtils::GetAttribute(pPort
, XML_ATTR_PORT_ID
);
214 auto it
= std::find_if(ports
.begin(), ports
.end(),
215 [&portId
](const CPortNode
& port
) { return port
.GetPortID() == portId
; });
216 if (it
!= ports
.end())
218 CPortNode
& port
= *it
;
220 DeserializePort(pPort
, port
);
225 void CPortManager::DeserializePort(const tinyxml2::XMLElement
* pPort
, CPortNode
& port
)
228 bool connected
= (XMLUtils::GetAttribute(pPort
, XML_ATTR_PORT_CONNECTED
) == "true");
229 port
.SetConnected(connected
);
232 const std::string activeControllerId
= XMLUtils::GetAttribute(pPort
, XML_ATTR_PORT_CONTROLLER
);
233 if (!port
.SetActiveController(activeControllerId
))
234 port
.SetConnected(false);
236 DeserializeControllers(pPort
, port
.GetCompatibleControllers());
239 void CPortManager::DeserializeControllers(const tinyxml2::XMLElement
* pPort
,
240 ControllerNodeVec
& controllers
)
242 for (const auto* pController
= pPort
->FirstChildElement(); pController
!= nullptr;
243 pController
= pController
->NextSiblingElement())
245 if (std::strcmp(pController
->Value(), XML_ELM_CONTROLLER
) != 0)
247 CLog::Log(LOGDEBUG
, "Inside <{}> tag: Ignoring <{}> tag", pPort
->Value(),
248 pController
->Value());
252 std::string controllerId
= XMLUtils::GetAttribute(pController
, XML_ATTR_CONTROLLER_ID
);
254 auto it
= std::find_if(controllers
.begin(), controllers
.end(),
255 [&controllerId
](const CControllerNode
& controller
)
256 { return controller
.GetController()->ID() == controllerId
; });
257 if (it
!= controllers
.end())
259 CControllerNode
& controller
= *it
;
261 DeserializeController(pController
, controller
);
266 void CPortManager::DeserializeController(const tinyxml2::XMLElement
* pController
,
267 CControllerNode
& controller
)
270 DeserializePorts(pController
, controller
.GetHub().GetPorts());
273 void CPortManager::SerializePorts(tinyxml2::XMLElement
& node
, const PortVec
& ports
)
275 auto doc
= node
.GetDocument();
276 for (const CPortNode
& port
: ports
)
278 auto portNode
= doc
->NewElement(XML_ELM_PORT
);
279 if (portNode
== nullptr)
282 SerializePort(*portNode
, port
);
284 node
.InsertEndChild(portNode
);
288 void CPortManager::SerializePort(tinyxml2::XMLElement
& portNode
, const CPortNode
& port
)
291 portNode
.SetAttribute(XML_ATTR_PORT_ID
, port
.GetPortID().c_str());
294 portNode
.SetAttribute(XML_ATTR_PORT_ADDRESS
, port
.GetAddress().c_str());
297 portNode
.SetAttribute(XML_ATTR_PORT_CONNECTED
, port
.IsConnected() ? "true" : "false");
300 if (port
.GetActiveController().GetController())
302 const std::string controllerId
= port
.GetActiveController().GetController()->ID();
303 portNode
.SetAttribute(XML_ATTR_PORT_CONTROLLER
, controllerId
.c_str());
306 // All compatible controllers
307 SerializeControllers(portNode
, port
.GetCompatibleControllers());
310 void CPortManager::SerializeControllers(tinyxml2::XMLElement
& portNode
,
311 const ControllerNodeVec
& controllers
)
313 for (const CControllerNode
& controller
: controllers
)
315 // Skip controller if it has no state
316 if (!HasState(controller
))
319 auto controllerNode
= portNode
.GetDocument()->NewElement(XML_ELM_CONTROLLER
);
320 if (controllerNode
== nullptr)
323 SerializeController(*controllerNode
, controller
);
325 portNode
.InsertEndChild(controllerNode
);
329 void CPortManager::SerializeController(tinyxml2::XMLElement
& controllerNode
,
330 const CControllerNode
& controller
)
333 if (controller
.GetController())
334 controllerNode
.SetAttribute(XML_ATTR_CONTROLLER_ID
, controller
.GetController()->ID().c_str());
337 SerializePorts(controllerNode
, controller
.GetHub().GetPorts());
340 bool CPortManager::HasState(const CPortNode
& port
)
342 // Ports have state (is connected / active controller)
346 bool CPortManager::HasState(const CControllerNode
& controller
)
348 // Check controller ports
349 for (const CPortNode
& port
: controller
.GetHub().GetPorts())
355 // Controller itself has no state