Branch data Line data Source code
1 : : #include "RestApiProvider.h"
2 : : #include "Logger.h"
3 : : #include <iostream>
4 : : #include <regex>
5 : :
6 : : #ifdef _WIN32
7 : : #include <windows.h>
8 : : #include <wininet.h>
9 : : #pragma comment(lib, "wininet.lib")
10 : : #endif
11 : :
12 : 655 : RestApiProvider& RestApiProvider::Get() {
13 [ + + + - : 655 : static RestApiProvider instance;
+ - - - ]
14 : 655 : return instance;
15 : : }
16 : :
17 : 1 : RestApiProvider::~RestApiProvider() {
18 : 1 : std::lock_guard<std::mutex> lock(m_threadMutex);
19 [ + - ]: 1 : if (m_requestThread.joinable()) {
20 : 1 : m_requestThread.join();
21 : : }
22 : 1 : }
23 : :
24 : 4 : void RestApiProvider::RequestSteeringRange(int port) {
25 [ + + ]: 4 : if (m_isRequesting.load()) return;
26 : :
27 [ + - ]: 3 : std::lock_guard<std::mutex> lock(m_threadMutex);
28 [ + - ]: 3 : if (m_requestThread.joinable()) {
29 [ + - ]: 3 : m_requestThread.join();
30 : : }
31 : :
32 : 3 : m_isRequesting = true;
33 [ + - ]: 6 : m_requestThread = std::thread([this, port]() {
34 : : try {
35 [ + - ]: 3 : this->PerformRequest(port);
36 : 0 : } catch (...) {
37 [ - - - - ]: 0 : Logger::Get().LogFile("RestApiProvider: Unexpected exception in request thread");
38 : 0 : }
39 : 3 : this->m_isRequesting = false;
40 : 6 : });
41 : 3 : }
42 : :
43 : 300 : float RestApiProvider::GetFallbackRangeDeg() const {
44 : 300 : return m_fallbackRangeDeg.load();
45 : : }
46 : :
47 : 0 : bool RestApiProvider::IsRequesting() const {
48 : 0 : return m_isRequesting.load();
49 : : }
50 : :
51 : 115 : void RestApiProvider::RequestManufacturer(int port, const std::string& vehicleName) {
52 [ + + ]: 115 : if (m_isRequesting.load()) return;
53 : :
54 [ + - ]: 107 : std::lock_guard<std::mutex> lock(m_threadMutex);
55 [ + + ]: 107 : if (m_requestThread.joinable()) {
56 [ + - ]: 106 : m_requestThread.join();
57 : : }
58 : :
59 : 107 : m_isRequesting = true;
60 [ + - + - ]: 214 : m_requestThread = std::thread([this, port, vehicleName]() {
61 : : try {
62 [ + - + - ]: 107 : this->PerformManufacturerRequest(port, vehicleName);
63 : 0 : } catch (...) {
64 [ - - - - ]: 0 : Logger::Get().LogFile("RestApiProvider: Unexpected exception in manufacturer request thread");
65 : 0 : }
66 : 107 : this->m_isRequesting = false;
67 : 214 : });
68 : 107 : }
69 : :
70 : 2 : std::string RestApiProvider::GetManufacturer() const {
71 [ + - ]: 2 : std::lock_guard<std::mutex> lock(m_manufacturerMutex);
72 [ + - ]: 4 : return m_manufacturer;
73 : 2 : }
74 : :
75 : 3 : void RestApiProvider::PerformRequest(int port) {
76 : 3 : std::string response;
77 : 3 : bool success = false;
78 : :
79 : : #ifdef _WIN32
80 : : HINTERNET hInternet = InternetOpenA("lmuFFB", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
81 : : if (hInternet) {
82 : : std::string url = "http://localhost:" + std::to_string(port) + "/rest/garage/getPlayerGarageData";
83 : : HINTERNET hConnect = InternetOpenUrlA(hInternet, url.c_str(), NULL, 0, INTERNET_FLAG_RELOAD, 0);
84 : : if (hConnect) {
85 : : char buffer[4096];
86 : : DWORD bytesRead;
87 : : while (InternetReadFile(hConnect, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) {
88 : : response.append(buffer, bytesRead);
89 : : }
90 : : InternetCloseHandle(hConnect);
91 : : success = true;
92 : : } else {
93 : : Logger::Get().LogFile("RestApiProvider: Failed to open URL (Port %d)", port);
94 : : }
95 : : InternetCloseHandle(hInternet);
96 : : } else {
97 : : Logger::Get().LogFile("RestApiProvider: Failed to initialize WinINet");
98 : : }
99 : : #else
100 [ + - + - ]: 3 : Logger::Get().LogFile("RestApiProvider: Mock request on Linux (Port %d)", port);
101 : : #endif
102 : :
103 [ - + - - : 3 : if (success && !response.empty()) {
- + ]
104 [ # # ]: 0 : float range = ParseSteeringLock(response);
105 [ # # ]: 0 : if (range > 0.0f) {
106 : 0 : m_fallbackRangeDeg = range;
107 [ # # # # ]: 0 : Logger::Get().LogFile("RestApiProvider: Retrieved steering range: %.1f deg", range);
108 : : } else {
109 [ # # # # ]: 0 : Logger::Get().LogFile("RestApiProvider: Could not parse VM_STEER_LOCK from response");
110 : : }
111 : : }
112 : 3 : }
113 : :
114 : 107 : void RestApiProvider::PerformManufacturerRequest(int port, std::string vehicleName) {
115 : 107 : std::string response;
116 : 107 : bool success = false;
117 : :
118 : : #ifdef _WIN32
119 : : HINTERNET hInternet = InternetOpenA("lmuFFB", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
120 : : if (hInternet) {
121 : : std::string url = "http://localhost:" + std::to_string(port) + "/rest/race/car";
122 : : HINTERNET hConnect = InternetOpenUrlA(hInternet, url.c_str(), NULL, 0, INTERNET_FLAG_RELOAD, 0);
123 : : if (hConnect) {
124 : : char buffer[8192];
125 : : DWORD bytesRead;
126 : : while (InternetReadFile(hConnect, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) {
127 : : response.append(buffer, bytesRead);
128 : : }
129 : : InternetCloseHandle(hConnect);
130 : : success = true;
131 : : } else {
132 : : Logger::Get().LogFile("RestApiProvider: Failed to open URL (Port %d)", port);
133 : : }
134 : : InternetCloseHandle(hInternet);
135 : : } else {
136 : : Logger::Get().LogFile("RestApiProvider: Failed to initialize WinINet");
137 : : }
138 : : #else
139 [ + - + - ]: 107 : Logger::Get().LogFile("RestApiProvider: Mock manufacturer request on Linux (Port %d)", port);
140 : : #endif
141 : :
142 [ - + - - : 107 : if (success && !response.empty()) {
- + ]
143 : : // Log the full response for diagnostics
144 [ # # # # ]: 0 : Logger::Get().LogFile("RestApiProvider: Full REST API response for car info: %s", response.c_str());
145 : :
146 [ # # ]: 0 : std::string manufacturer = ParseManufacturer(response, vehicleName);
147 : :
148 [ # # ]: 0 : std::lock_guard<std::mutex> lock(m_manufacturerMutex);
149 [ # # ]: 0 : m_manufacturer = manufacturer;
150 : 0 : m_hasManufacturer = true;
151 [ # # # # ]: 0 : Logger::Get().LogFile("RestApiProvider: Identified manufacturer from REST API: %s", m_manufacturer.c_str());
152 : 0 : }
153 : 107 : }
154 : :
155 : 4 : std::string RestApiProvider::ParseManufacturer(const std::string& json, const std::string& vehicleName) {
156 : : // LMU JSON contains "desc" and "manufacturer"
157 : : // Match "desc": "vehicleName" and extract "manufacturer"
158 : :
159 [ + - + - ]: 4 : size_t descPos = json.find("\"desc\":\"" + vehicleName + "\"");
160 [ + - ]: 4 : if (descPos == std::string::npos) {
161 : : // Try loose match if exact match fails (shared memory might have extra spaces or truncated)
162 : : // This is a heuristic.
163 : 4 : descPos = json.find("\"desc\"");
164 [ + + ]: 10 : while (descPos != std::string::npos) {
165 : 9 : size_t startQuote = json.find('\"', descPos + 6);
166 : 9 : size_t endQuote = json.find('\"', startQuote + 1);
167 [ + - + - ]: 9 : if (startQuote != std::string::npos && endQuote != std::string::npos) {
168 [ + - ]: 9 : std::string desc = json.substr(startQuote + 1, endQuote - startQuote - 1);
169 [ + + - + : 9 : if (desc.find(vehicleName) != std::string::npos || vehicleName.find(desc) != std::string::npos) {
+ + ]
170 : 3 : break;
171 : : }
172 [ + + ]: 9 : }
173 : 6 : descPos = json.find("\"desc\"", descPos + 1);
174 : : }
175 : : }
176 : :
177 [ + + + - ]: 6 : if (descPos == std::string::npos) return "Unknown";
178 : :
179 : : // Now find "manufacturer" near this desc
180 : : // Search within 1024 characters after desc
181 : 3 : size_t manufacturerPos = json.find("\"manufacturer\"", descPos);
182 [ + - - + ]: 3 : if (manufacturerPos == std::string::npos || (manufacturerPos - descPos) > 1024) {
183 : : // Search backwards just in case
184 : 0 : manufacturerPos = json.rfind("\"manufacturer\"", descPos);
185 : : }
186 : :
187 [ - + - - ]: 3 : if (manufacturerPos == std::string::npos) return "Unknown";
188 : :
189 : 3 : size_t colonPos = json.find(':', manufacturerPos);
190 : 3 : size_t startQuote = json.find('\"', colonPos);
191 : 3 : size_t endQuote = json.find('\"', startQuote + 1);
192 : :
193 [ + - + - ]: 3 : if (startQuote != std::string::npos && endQuote != std::string::npos) {
194 : 3 : return json.substr(startQuote + 1, endQuote - startQuote - 1);
195 : : }
196 : :
197 [ # # ]: 0 : return "Unknown";
198 : : }
199 : :
200 : 5 : float RestApiProvider::ParseSteeringLock(const std::string& json) {
201 : : // Look for "VM_STEER_LOCK" and then "stringValue"
202 : : // Example: "VM_STEER_LOCK" : { ... "stringValue" : "540 deg" ... }
203 : :
204 : : // 1. Find the property
205 : 5 : size_t propPos = json.find("\"VM_STEER_LOCK\"");
206 [ + + ]: 5 : if (propPos == std::string::npos) return 0.0f;
207 : :
208 : : // 2. Find "stringValue" within the next few hundred characters
209 : 3 : size_t searchLimit = 512;
210 [ + - ]: 3 : std::string sub = json.substr(propPos, searchLimit);
211 : 3 : size_t valPos = sub.find("\"stringValue\"");
212 [ - + ]: 3 : if (valPos == std::string::npos) return 0.0f;
213 : :
214 : : // 3. Extract the value string
215 : 3 : size_t colonPos = sub.find(':', valPos);
216 [ - + ]: 3 : if (colonPos == std::string::npos) return 0.0f;
217 : :
218 : 3 : size_t startQuote = sub.find('\"', colonPos);
219 [ - + ]: 3 : if (startQuote == std::string::npos) return 0.0f;
220 : :
221 : 3 : size_t endQuote = sub.find('\"', startQuote + 1);
222 [ - + ]: 3 : if (endQuote == std::string::npos) return 0.0f;
223 : :
224 [ + - ]: 3 : std::string valueStr = sub.substr(startQuote + 1, endQuote - startQuote - 1);
225 : :
226 : : // 4. Extract numeric value using regex
227 [ + - ]: 3 : std::regex re(R"(\d*\.?\d+)");
228 : 3 : std::smatch match;
229 [ + - + - ]: 3 : if (std::regex_search(valueStr, match, re)) {
230 : : try {
231 [ + - + - ]: 3 : return std::stof(match.str());
232 : 0 : } catch (...) {
233 : 0 : return 0.0f;
234 [ - - ]: 0 : }
235 : : }
236 : :
237 : 0 : return 0.0f;
238 : 3 : }
|