7 |
*/ |
*/ |
8 |
|
|
9 |
#include "LSCPClient.h" |
#include "LSCPClient.h" |
10 |
|
#include "lscp.h" |
11 |
#include <strings.h> |
#include <strings.h> |
12 |
|
|
13 |
|
/** |
14 |
|
* This function is called by the LSCPClient receiving thread to identify |
15 |
|
* whether the received line is a) just a single line response or b) the first |
16 |
|
* line of a multi line response. LSCPClient will provide this information via |
17 |
|
* its multiLine() and messageComplete() methods, which are accessed by the |
18 |
|
* application's main thread to find out whether it should wait until all lines |
19 |
|
* of the multi line message were completely received. This strategy (to let |
20 |
|
* the main app thread wait until a multi line message is really entirely |
21 |
|
* received) is preferable since it would otherwise require a very complex code |
22 |
|
* with a bunch of state variables in the main apps loop, which would also make |
23 |
|
* it very error prone. |
24 |
|
* |
25 |
|
* @param line - input: the input line to check |
26 |
|
* @param skipLine - output: whether @a line shall be silently ignored |
27 |
|
* @returns true if @a line indicates a multi line response |
28 |
|
*/ |
29 |
|
static bool _lscpLineIndicatesMultiLineResponse(const String& line, bool& skipLine) { |
30 |
|
static const String multiLineKey = LSCP_SHK_EXPECT_MULTI_LINE; |
31 |
|
static const String lscpRefDocKey = "SHD:1"; |
32 |
|
if (line.substr(0, multiLineKey.length()) == multiLineKey) { |
33 |
|
skipLine = true; |
34 |
|
return true; |
35 |
|
} |
36 |
|
if (line.substr(0, lscpRefDocKey.length()) == lscpRefDocKey) { |
37 |
|
skipLine = false; |
38 |
|
return true; |
39 |
|
} |
40 |
|
skipLine = false; |
41 |
|
return false; |
42 |
|
} |
43 |
|
|
44 |
|
/** @brief Default constructor. |
45 |
|
* |
46 |
|
* Initializes a yet unconnected LSCP client. You need to call connect() |
47 |
|
* afterwards to actually let it connect to some LSCP server for being able to |
48 |
|
* use the client for something useful. |
49 |
|
*/ |
50 |
LSCPClient::LSCPClient() : |
LSCPClient::LSCPClient() : |
51 |
Thread(false, false, 1, -1), |
Thread(false, false, 1, -1), |
52 |
hSocket(-1), m_callback(NULL), m_errorCallback(NULL), |
hSocket(-1), m_callback(NULL), m_errorCallback(NULL), |
53 |
m_multiLineExpected(false), m_multiLineComplete(false) |
m_multiLineExpected(false) |
54 |
{ |
{ |
55 |
} |
} |
56 |
|
|
57 |
|
/** @brief Destructor. |
58 |
|
* |
59 |
|
* Disconnects this client automatically if it is currently still connected to |
60 |
|
* some LSCP server. |
61 |
|
*/ |
62 |
LSCPClient::~LSCPClient() { |
LSCPClient::~LSCPClient() { |
63 |
disconnect(); |
disconnect(); |
64 |
} |
} |
65 |
|
|
66 |
|
/** |
67 |
|
* Let this client connect to a (already running) LSCP server listening on |
68 |
|
* host name @a host and TCP port number @a port. If a connection was |
69 |
|
* successfully established, this method will also start a thread which will |
70 |
|
* constantly wait for receiving new data sent by the LSCP server. This thread |
71 |
|
* will store all received data in an internal buffer which can then be pulled |
72 |
|
* out with popLine(). |
73 |
|
* |
74 |
|
* @param host - host name of LSCP server |
75 |
|
* @param port - TCP port number of LSCP server |
76 |
|
* @returns true if connection was successful, false on errors. |
77 |
|
*/ |
78 |
bool LSCPClient::connect(String host, int port) { |
bool LSCPClient::connect(String host, int port) { |
79 |
m_lineBuffer.clear(); |
m_lineBuffer.clear(); |
80 |
m_lines.clear(); |
m_lines.clear(); |
106 |
return true; // success |
return true; // success |
107 |
} |
} |
108 |
|
|
109 |
|
/** |
110 |
|
* Disconnect this client from the currently connected LSCP server. If there |
111 |
|
* is no connection, then this method does nothing. |
112 |
|
*/ |
113 |
void LSCPClient::disconnect() { |
void LSCPClient::disconnect() { |
114 |
if (hSocket >= 0) { |
if (hSocket >= 0) { |
115 |
StopThread(); |
StopThread(); |
118 |
} |
} |
119 |
} |
} |
120 |
|
|
121 |
|
/** |
122 |
|
* Returns true if this LSCP client is currently connected to a LSCP server. |
123 |
|
*/ |
124 |
bool LSCPClient::isConnected() const { |
bool LSCPClient::isConnected() const { |
125 |
return hSocket >= 0; |
return hSocket >= 0; |
126 |
} |
} |
127 |
|
|
128 |
|
/** |
129 |
|
* Send one byte given by @a c to the connected LSCP server asynchronously. |
130 |
|
* |
131 |
|
* @param c - byte (character) to be sent |
132 |
|
*/ |
133 |
bool LSCPClient::send(char c) { |
bool LSCPClient::send(char c) { |
134 |
String s; |
String s; |
135 |
s += c; |
s += c; |
136 |
return send(s); |
return send(s); |
137 |
} |
} |
138 |
|
|
139 |
|
/** |
140 |
|
* Send a sequence of bytes given by @a s to the connected LSCP server |
141 |
|
* asynchronously. |
142 |
|
* |
143 |
|
* @param s - sequence of bytes (string) to be sent |
144 |
|
*/ |
145 |
bool LSCPClient::send(String s) { |
bool LSCPClient::send(String s) { |
146 |
|
//lscpLog("[send] '%s'\n", s.c_str()); |
147 |
if (!isConnected()) return false; |
if (!isConnected()) return false; |
148 |
int n = ::write(hSocket, &s[0], s.size()); |
int n = ::write(hSocket, &s[0], s.size()); |
149 |
return n == s.size(); |
return n == s.size(); |
150 |
} |
} |
151 |
|
|
152 |
|
/** |
153 |
|
* Send a sequence of bytes given by @a s to the connected LSCP server |
154 |
|
* synchronously. This method will then block until a response line was |
155 |
|
* received from the LSCP server and will return that response line as result |
156 |
|
* to this method. Since this currently assumes a single line response, it |
157 |
|
* should just be used for sending LSCP commands which will exepct a single |
158 |
|
* line response from the LSCP server. |
159 |
|
* |
160 |
|
* @param s - sequence of bytes (string) to be sent |
161 |
|
* @returns response of LSCP server |
162 |
|
*/ |
163 |
String LSCPClient::sendCommandSync(String s) { |
String LSCPClient::sendCommandSync(String s) { |
164 |
m_linesMutex.Lock(); |
m_linesMutex.Lock(); |
165 |
m_lines.clear(); |
m_lines.clear(); |
170 |
m_sync.WaitIf(true); |
m_sync.WaitIf(true); |
171 |
|
|
172 |
m_linesMutex.Lock(); |
m_linesMutex.Lock(); |
173 |
String sResponse = m_lines.back(); |
String sResponse = m_lines.back().data; |
174 |
m_lines.clear(); |
m_lines.clear(); |
175 |
m_linesMutex.Unlock(); |
m_linesMutex.Unlock(); |
176 |
|
|
177 |
return sResponse; |
return sResponse; |
178 |
} |
} |
179 |
|
|
180 |
|
/** |
181 |
|
* Pulls out and returns the next line received from the LSCP server. If the |
182 |
|
* return value of this method evaluates to false, then there is currently no |
183 |
|
* new line received. The line returned will be removed from the internal |
184 |
|
* receive buffer. If that's not what you want, then use lookAheadLine() |
185 |
|
* instead. |
186 |
|
*/ |
187 |
optional<String> LSCPClient::popLine() { |
optional<String> LSCPClient::popLine() { |
188 |
String s; |
String s; |
189 |
LockGuard guard(m_linesMutex); |
LockGuard guard(m_linesMutex); |
190 |
if (m_lines.empty()) return optional<String>::nothing; |
if (m_lines.empty()) return optional<String>::nothing; |
191 |
s = m_lines.front(); |
s = m_lines.front().data; |
192 |
m_lines.pop_front(); |
m_lines.pop_front(); |
|
//FIXME: this is incorrect when having multiple complete messages in the queue |
|
|
if (m_multiLineExpected && s.substr(0, 1) == ".") |
|
|
m_multiLineExpected = m_multiLineComplete = false; |
|
193 |
return s; |
return s; |
194 |
} |
} |
195 |
|
|
196 |
|
/** |
197 |
|
* Use this method instead of popLine() in case you want to access received |
198 |
|
* line(s) without removing them from the internal buffer. |
199 |
|
* |
200 |
|
* @param index - offset of line to be accessed (0 being the oldest line |
201 |
|
* received) |
202 |
|
*/ |
203 |
optional<String> LSCPClient::lookAheadLine(int index) { |
optional<String> LSCPClient::lookAheadLine(int index) { |
204 |
LockGuard guard(m_linesMutex); |
LockGuard guard(m_linesMutex); |
205 |
if (index < 0 || index >= m_lines.size()) |
if (index < 0 || index >= m_lines.size()) |
206 |
return optional<String>::nothing; |
return optional<String>::nothing; |
207 |
std::list<String>::iterator it = m_lines.begin(); |
std::list<Line>::iterator it = m_lines.begin(); |
208 |
for (int i = 0; i < index; ++i) ++it; |
for (int i = 0; i < index; ++i) ++it; |
209 |
String s = *it; |
String s = (*it).data; |
210 |
return s; |
return s; |
211 |
} |
} |
212 |
|
|
213 |
|
/** |
214 |
|
* Returns true if the received line(s), waiting to be pulled out with |
215 |
|
* popLine(), is/are completely received. So if the next line waiting to be |
216 |
|
* pulled out with popLine() is a single line response, then this function |
217 |
|
* returns true. If the next line waiting to be pulled out with popLine() is |
218 |
|
* considered a part of multi line response though, this function only returns |
219 |
|
* true if all lines of that expected multi line response were entirely |
220 |
|
* received already. |
221 |
|
* |
222 |
|
* @see multiLine() |
223 |
|
*/ |
224 |
bool LSCPClient::messageComplete() { |
bool LSCPClient::messageComplete() { |
|
if (!lineAvailable()) return false; |
|
225 |
LockGuard guard(m_linesMutex); |
LockGuard guard(m_linesMutex); |
226 |
if (!m_multiLineExpected) return true; |
if (m_lines.empty()) { |
227 |
return m_multiLineComplete; |
//lscpLog("\t[messageComplete false - buffer empty]\n"); |
228 |
|
return false; |
229 |
|
} |
230 |
|
if (!m_lines.front().isMultiLine) { |
231 |
|
//lscpLog("\t[messageComplete true - single line]\n"); |
232 |
|
return true; |
233 |
|
} |
234 |
|
|
235 |
|
// // just for debugging purposes ... |
236 |
|
// int k = 0; |
237 |
|
// for (std::list<Line>::const_iterator it = m_lines.begin(); |
238 |
|
// it != m_lines.end(); ++it, ++k) |
239 |
|
// { |
240 |
|
// lscpLog("\t[messageComplete k=%d : mul=%d cmpl=%d data='%s']\n", k, (*it).isMultiLine, (*it).isMultiLineComplete, (*it).data.c_str()); |
241 |
|
// } |
242 |
|
|
243 |
|
// so next line is part of a multi line response, check if it's complete ... |
244 |
|
for (std::list<Line>::const_iterator it = m_lines.begin(); |
245 |
|
it != m_lines.end(); ++it) |
246 |
|
{ |
247 |
|
if ((*it).isMultiLineComplete) return true; |
248 |
|
} |
249 |
|
return false; |
250 |
} |
} |
251 |
|
|
252 |
|
/** |
253 |
|
* Returns true if the next line to be pulled out with popLine() is considered |
254 |
|
* a single line response, it returns false if the next line is a part of a |
255 |
|
* multi line response instead. In the latter case you should call multiLine() |
256 |
|
* to check whether all lines of that multi line response were already received |
257 |
|
* before pulling them out with popLine(). |
258 |
|
* |
259 |
|
* @see messageComplete() |
260 |
|
*/ |
261 |
bool LSCPClient::multiLine() { |
bool LSCPClient::multiLine() { |
262 |
//FIXME: returns falsely "true" in case the first message is single-line response but there is already a multi-line response in the FIFO |
LockGuard guard(m_linesMutex); |
263 |
return m_multiLineExpected; |
if (m_lines.empty()) return false; |
264 |
|
bool bIsMultiLine = m_lines.front().isMultiLine; |
265 |
|
return bIsMultiLine; |
266 |
} |
} |
267 |
|
|
268 |
|
/** |
269 |
|
* Returns true if new line(s) were received from LSCP server, waiting to be |
270 |
|
* pulled out with popLine(). |
271 |
|
*/ |
272 |
bool LSCPClient::lineAvailable() const { |
bool LSCPClient::lineAvailable() const { |
273 |
return !m_lines.empty(); // is thread safe |
return !m_lines.empty(); // is thread safe |
274 |
} |
} |
275 |
|
|
276 |
|
/** |
277 |
|
* You may call this method to register a callback function which shall be |
278 |
|
* notified if new data was received from the connected LSCP server. |
279 |
|
*/ |
280 |
void LSCPClient::setCallback(Callback_t fn) { |
void LSCPClient::setCallback(Callback_t fn) { |
281 |
m_callback = fn; |
m_callback = fn; |
282 |
} |
} |
283 |
|
|
284 |
|
/** |
285 |
|
* You may call this method to register a callback function which shall be |
286 |
|
* notified if some kind of network error occurred. |
287 |
|
*/ |
288 |
void LSCPClient::setErrorCallback(Callback_t fn) { |
void LSCPClient::setErrorCallback(Callback_t fn) { |
289 |
m_errorCallback = fn; |
m_errorCallback = fn; |
290 |
} |
} |
291 |
|
|
292 |
|
/** |
293 |
|
* This method is only called by the internal client thread: a call to this |
294 |
|
* method blocks until a complete new line was received from the LSCP server. |
295 |
|
*/ |
296 |
optional<String> LSCPClient::receiveLine() { |
optional<String> LSCPClient::receiveLine() { |
297 |
if (!isConnected()) return optional<String>::nothing; |
if (!isConnected()) return optional<String>::nothing; |
298 |
for (char c; true; ) { |
for (char c; true; ) { |
310 |
return optional<String>::nothing; |
return optional<String>::nothing; |
311 |
} |
} |
312 |
|
|
313 |
|
/** |
314 |
|
* Client's internal receiving thread's loop. It does nothing else than waiting |
315 |
|
* for new data sent by LSCP server and puts the received lines into an |
316 |
|
* internal FIFO buffer. |
317 |
|
*/ |
318 |
int LSCPClient::Main() { |
int LSCPClient::Main() { |
|
static const String multiLineKey = LSCP_SHK_EXPECT_MULTI_LINE; |
|
319 |
while (true) { |
while (true) { |
320 |
optional<String> pLine = receiveLine(); |
optional<String> pLine = receiveLine(); |
321 |
if (pLine) { |
if (pLine) { // if there was a line received ... |
322 |
|
//lscpLog("[client receiveLine] '%s'\n", pLine->c_str()); |
323 |
String s = *pLine; |
String s = *pLine; |
324 |
//printf("->line '%s'\n", s.c_str()); |
|
325 |
m_linesMutex.Lock(); |
// check whether this is a multi line data ... |
326 |
if (s.substr(0, multiLineKey.length()) == multiLineKey) { |
bool bSkipLine = false; |
327 |
|
bool bMultiLineComplete = false; |
328 |
|
if (!m_multiLineExpected && _lscpLineIndicatesMultiLineResponse(s, bSkipLine)) { |
329 |
m_multiLineExpected = true; |
m_multiLineExpected = true; |
330 |
m_multiLineComplete = false; |
} else if (m_multiLineExpected && s.substr(0, 1) == ".") { |
331 |
} else { |
bMultiLineComplete = true; |
332 |
if (m_multiLineExpected && s.substr(0, 1) == ".") { |
} |
333 |
m_multiLineComplete = true; |
|
334 |
} |
// store received line in internal FIFO buffer |
335 |
m_lines.push_back(s); |
if (!bSkipLine) { |
336 |
|
Line l = { s, m_multiLineExpected, bMultiLineComplete }; |
337 |
|
m_linesMutex.Lock(); |
338 |
|
m_lines.push_back(l); |
339 |
|
m_linesMutex.Unlock(); |
340 |
|
} |
341 |
|
|
342 |
|
// if this was the last line of a multi line response, reset |
343 |
|
// for next run |
344 |
|
if (m_multiLineExpected && bMultiLineComplete) { |
345 |
|
m_multiLineExpected = false; |
346 |
} |
} |
|
m_linesMutex.Unlock(); |
|
347 |
|
|
348 |
if (m_sync.GetUnsafe()) m_sync.Set(false); |
if (m_sync.GetUnsafe()) m_sync.Set(false); |
349 |
else if (m_callback) (*m_callback)(this); |
else if (m_callback) (*m_callback)(this); |