/[svn]/linuxsampler/trunk/src/shell/LSCPClient.cpp
ViewVC logotype

Contents of /linuxsampler/trunk/src/shell/LSCPClient.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2535 - (show annotations) (download)
Tue Apr 15 19:35:35 2014 UTC (10 years ago) by schoenebeck
File size: 11670 byte(s)
* LSCP server: optimized server side processing of LSCP shell tasks (caused
  a sluggish behavior, i.e. when using arrow up/down keys in LSCP shell).
* LSCP shell: fixed crash on server disconnection.
* LSCP shell: fixed trash printed on terminal for LSCP documentation
  sometimes.
* Automake: tried to address a compilation error with automake 1.9
  (see bug #216), seems that it did not fix it though.
* Bumped version (1.0.0.svn38).

1 /*
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 #include "lscp.h"
11 #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() :
51 Thread(false, false, 1, -1),
52 hSocket(-1), m_callback(NULL), m_errorCallback(NULL),
53 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() {
63 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) {
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 std::cerr << "Is linuxsampler running and listening on port " << port << " ?\n";
102 disconnect();
103 return false;
104 }
105 StartThread();
106 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() {
114 if (hSocket >= 0) {
115 StopThread();
116 ::close(hSocket);
117 hSocket = -1;
118 }
119 }
120
121 /**
122 * Returns true if this LSCP client is currently connected to a LSCP server.
123 */
124 bool LSCPClient::isConnected() const {
125 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) {
134 String s;
135 s += c;
136 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) {
146 //lscpLog("[send] '%s'\n", s.c_str());
147 if (!isConnected()) return false;
148 int n = ::write(hSocket, &s[0], s.size());
149 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) {
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 String sResponse = m_lines.back().data;
174 m_lines.clear();
175 m_linesMutex.Unlock();
176
177 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() {
188 String s;
189 LockGuard guard(m_linesMutex);
190 if (m_lines.empty()) return optional<String>::nothing;
191 s = m_lines.front().data;
192 m_lines.pop_front();
193 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) {
204 LockGuard guard(m_linesMutex);
205 if (index < 0 || index >= m_lines.size())
206 return optional<String>::nothing;
207 std::list<Line>::iterator it = m_lines.begin();
208 for (int i = 0; i < index; ++i) ++it;
209 String s = (*it).data;
210 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() {
225 LockGuard guard(m_linesMutex);
226 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 }
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() {
262 LockGuard guard(m_linesMutex);
263 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 {
273 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) {
281 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) {
289 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() {
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 /**
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() {
319 while (true) {
320 optional<String> pLine = receiveLine();
321 if (pLine) { // if there was a line received ...
322 //lscpLog("[client receiveLine] '%s'\n", pLine->c_str());
323 String s = *pLine;
324
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 m_multiLineExpected = true;
330 } else if (m_multiLineExpected && s.substr(0, 1) == ".") {
331 bMultiLineComplete = true;
332 }
333
334 // 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 if (m_sync.GetUnsafe()) m_sync.Set(false);
349 else if (m_callback) (*m_callback)(this);
350 } else if (m_errorCallback) (*m_errorCallback)(this);
351 TestCancel();
352 }
353 return 0; // just to avoid a warning with some old compilers
354 }

  ViewVC Help
Powered by ViewVC