1 |
schoenebeck |
2515 |
/* |
2 |
|
|
* LSCP Shell |
3 |
|
|
* |
4 |
|
|
* Copyright (c) 2014 Christian Schoenebeck |
5 |
|
|
* |
6 |
|
|
* This program is part of LinuxSampler and released under the same terms. |
7 |
|
|
*/ |
8 |
|
|
|
9 |
|
|
#include "LSCPClient.h" |
10 |
schoenebeck |
2535 |
#include "lscp.h" |
11 |
schoenebeck |
2515 |
#include <strings.h> |
12 |
|
|
|
13 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
LSCPClient::LSCPClient() : |
51 |
|
|
Thread(false, false, 1, -1), |
52 |
schoenebeck |
2532 |
hSocket(-1), m_callback(NULL), m_errorCallback(NULL), |
53 |
schoenebeck |
2535 |
m_multiLineExpected(false) |
54 |
schoenebeck |
2515 |
{ |
55 |
|
|
} |
56 |
|
|
|
57 |
schoenebeck |
2535 |
/** @brief Destructor. |
58 |
|
|
* |
59 |
|
|
* Disconnects this client automatically if it is currently still connected to |
60 |
|
|
* some LSCP server. |
61 |
|
|
*/ |
62 |
schoenebeck |
2515 |
LSCPClient::~LSCPClient() { |
63 |
|
|
disconnect(); |
64 |
|
|
} |
65 |
|
|
|
66 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
bool LSCPClient::connect(String host, int port) { |
79 |
|
|
m_lineBuffer.clear(); |
80 |
|
|
m_lines.clear(); |
81 |
|
|
// resolve given host name |
82 |
|
|
hostent* server = ::gethostbyname(host.c_str()); |
83 |
|
|
if (!server) { |
84 |
|
|
std::cerr << "Error: Could not resolve host \"" << host << "\".\n"; |
85 |
|
|
return false; |
86 |
|
|
} |
87 |
|
|
// create local TCP socket |
88 |
|
|
hSocket = ::socket(AF_INET, SOCK_STREAM, 0); |
89 |
|
|
if (hSocket < 0) { |
90 |
|
|
std::cerr << "Error: Could not create local socket.\n"; |
91 |
|
|
return false; |
92 |
|
|
} |
93 |
|
|
// TCP connect to server |
94 |
|
|
sockaddr_in addr; |
95 |
|
|
bzero((char*)&addr, sizeof(addr)); |
96 |
|
|
addr.sin_family = AF_INET; |
97 |
|
|
bcopy((char*)server->h_addr, (char*)&addr.sin_addr.s_addr, server->h_length); |
98 |
|
|
addr.sin_port = htons(port); |
99 |
|
|
if (::connect(hSocket, (sockaddr*)&addr, sizeof(addr)) < 0) { |
100 |
|
|
std::cerr << "Error: Could not connect to host \"" << host << "\".\n"; |
101 |
schoenebeck |
2517 |
std::cerr << "Is linuxsampler running and listening on port " << port << " ?\n"; |
102 |
schoenebeck |
2515 |
disconnect(); |
103 |
|
|
return false; |
104 |
|
|
} |
105 |
|
|
StartThread(); |
106 |
|
|
return true; // success |
107 |
|
|
} |
108 |
|
|
|
109 |
schoenebeck |
2535 |
/** |
110 |
|
|
* Disconnect this client from the currently connected LSCP server. If there |
111 |
|
|
* is no connection, then this method does nothing. |
112 |
|
|
*/ |
113 |
schoenebeck |
2515 |
void LSCPClient::disconnect() { |
114 |
|
|
if (hSocket >= 0) { |
115 |
|
|
StopThread(); |
116 |
|
|
::close(hSocket); |
117 |
|
|
hSocket = -1; |
118 |
|
|
} |
119 |
|
|
} |
120 |
|
|
|
121 |
schoenebeck |
2535 |
/** |
122 |
|
|
* Returns true if this LSCP client is currently connected to a LSCP server. |
123 |
|
|
*/ |
124 |
schoenebeck |
2515 |
bool LSCPClient::isConnected() const { |
125 |
|
|
return hSocket >= 0; |
126 |
|
|
} |
127 |
|
|
|
128 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
bool LSCPClient::send(char c) { |
134 |
|
|
String s; |
135 |
|
|
s += c; |
136 |
|
|
return send(s); |
137 |
|
|
} |
138 |
|
|
|
139 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
bool LSCPClient::send(String s) { |
146 |
schoenebeck |
2535 |
//lscpLog("[send] '%s'\n", s.c_str()); |
147 |
schoenebeck |
2515 |
if (!isConnected()) return false; |
148 |
|
|
int n = ::write(hSocket, &s[0], s.size()); |
149 |
|
|
return n == s.size(); |
150 |
|
|
} |
151 |
|
|
|
152 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
String LSCPClient::sendCommandSync(String s) { |
164 |
|
|
m_linesMutex.Lock(); |
165 |
|
|
m_lines.clear(); |
166 |
|
|
m_linesMutex.Unlock(); |
167 |
|
|
|
168 |
|
|
m_sync.Set(true); |
169 |
|
|
if (!send(s + "\n")) return ""; |
170 |
|
|
m_sync.WaitIf(true); |
171 |
|
|
|
172 |
|
|
m_linesMutex.Lock(); |
173 |
schoenebeck |
2535 |
String sResponse = m_lines.back().data; |
174 |
schoenebeck |
2515 |
m_lines.clear(); |
175 |
|
|
m_linesMutex.Unlock(); |
176 |
|
|
|
177 |
|
|
return sResponse; |
178 |
|
|
} |
179 |
|
|
|
180 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
optional<String> LSCPClient::popLine() { |
188 |
|
|
String s; |
189 |
|
|
LockGuard guard(m_linesMutex); |
190 |
|
|
if (m_lines.empty()) return optional<String>::nothing; |
191 |
schoenebeck |
2535 |
s = m_lines.front().data; |
192 |
schoenebeck |
2515 |
m_lines.pop_front(); |
193 |
|
|
return s; |
194 |
|
|
} |
195 |
|
|
|
196 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
optional<String> LSCPClient::lookAheadLine(int index) { |
204 |
|
|
LockGuard guard(m_linesMutex); |
205 |
|
|
if (index < 0 || index >= m_lines.size()) |
206 |
|
|
return optional<String>::nothing; |
207 |
schoenebeck |
2535 |
std::list<Line>::iterator it = m_lines.begin(); |
208 |
schoenebeck |
2515 |
for (int i = 0; i < index; ++i) ++it; |
209 |
schoenebeck |
2535 |
String s = (*it).data; |
210 |
schoenebeck |
2515 |
return s; |
211 |
|
|
} |
212 |
|
|
|
213 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
bool LSCPClient::messageComplete() { |
225 |
|
|
LockGuard guard(m_linesMutex); |
226 |
schoenebeck |
2535 |
if (m_lines.empty()) { |
227 |
|
|
//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 |
schoenebeck |
2515 |
} |
251 |
|
|
|
252 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
bool LSCPClient::multiLine() { |
262 |
schoenebeck |
2535 |
LockGuard guard(m_linesMutex); |
263 |
|
|
if (m_lines.empty()) return false; |
264 |
|
|
bool bIsMultiLine = m_lines.front().isMultiLine; |
265 |
|
|
return bIsMultiLine; |
266 |
schoenebeck |
2515 |
} |
267 |
|
|
|
268 |
schoenebeck |
2535 |
/** |
269 |
|
|
* Returns true if new line(s) were received from LSCP server, waiting to be |
270 |
|
|
* pulled out with popLine(). |
271 |
|
|
*/ |
272 |
schoenebeck |
2515 |
bool LSCPClient::lineAvailable() const { |
273 |
|
|
return !m_lines.empty(); // is thread safe |
274 |
|
|
} |
275 |
|
|
|
276 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
void LSCPClient::setCallback(Callback_t fn) { |
281 |
|
|
m_callback = fn; |
282 |
|
|
} |
283 |
|
|
|
284 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2532 |
void LSCPClient::setErrorCallback(Callback_t fn) { |
289 |
|
|
m_errorCallback = fn; |
290 |
|
|
} |
291 |
|
|
|
292 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
optional<String> LSCPClient::receiveLine() { |
297 |
|
|
if (!isConnected()) return optional<String>::nothing; |
298 |
|
|
for (char c; true; ) { |
299 |
|
|
int n = ::read(hSocket, &c, 1); |
300 |
|
|
if (n < 1) return optional<String>::nothing; |
301 |
|
|
if (c == '\r') continue; |
302 |
|
|
if (c == '\n') { |
303 |
|
|
String s = m_lineBuffer; |
304 |
|
|
m_lineBuffer.clear(); |
305 |
|
|
return s; |
306 |
|
|
} |
307 |
|
|
//printf("->%c\n", c); |
308 |
|
|
m_lineBuffer += c; |
309 |
|
|
} |
310 |
|
|
return optional<String>::nothing; |
311 |
|
|
} |
312 |
|
|
|
313 |
schoenebeck |
2535 |
/** |
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 |
schoenebeck |
2515 |
int LSCPClient::Main() { |
319 |
|
|
while (true) { |
320 |
|
|
optional<String> pLine = receiveLine(); |
321 |
schoenebeck |
2535 |
if (pLine) { // if there was a line received ... |
322 |
|
|
//lscpLog("[client receiveLine] '%s'\n", pLine->c_str()); |
323 |
schoenebeck |
2515 |
String s = *pLine; |
324 |
schoenebeck |
2535 |
|
325 |
|
|
// check whether this is a multi line data ... |
326 |
|
|
bool bSkipLine = false; |
327 |
|
|
bool bMultiLineComplete = false; |
328 |
|
|
if (!m_multiLineExpected && _lscpLineIndicatesMultiLineResponse(s, bSkipLine)) { |
329 |
schoenebeck |
2515 |
m_multiLineExpected = true; |
330 |
schoenebeck |
2535 |
} else if (m_multiLineExpected && s.substr(0, 1) == ".") { |
331 |
|
|
bMultiLineComplete = true; |
332 |
schoenebeck |
2515 |
} |
333 |
|
|
|
334 |
schoenebeck |
2535 |
// store received line in internal FIFO buffer |
335 |
|
|
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 |
|
|
} |
347 |
|
|
|
348 |
schoenebeck |
2515 |
if (m_sync.GetUnsafe()) m_sync.Set(false); |
349 |
|
|
else if (m_callback) (*m_callback)(this); |
350 |
schoenebeck |
2532 |
} else if (m_errorCallback) (*m_errorCallback)(this); |
351 |
schoenebeck |
2515 |
TestCancel(); |
352 |
|
|
} |
353 |
|
|
return 0; // just to avoid a warning with some old compilers |
354 |
|
|
} |