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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 2534 by schoenebeck, Wed Mar 5 17:29:15 2014 UTC revision 2535 by schoenebeck, Tue Apr 15 19:35:35 2014 UTC
# Line 7  Line 7 
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();
# Line 52  bool LSCPClient::connect(String host, in Line 106  bool LSCPClient::connect(String host, in
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();
# Line 60  void LSCPClient::disconnect() { Line 118  void LSCPClient::disconnect() {
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();
# Line 86  String LSCPClient::sendCommandSync(Strin Line 170  String LSCPClient::sendCommandSync(Strin
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; ) {
# Line 156  optional<String> LSCPClient::receiveLine Line 310  optional<String> LSCPClient::receiveLine
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);

Legend:
Removed from v.2534  
changed lines
  Added in v.2535

  ViewVC Help
Powered by ViewVC