source: trunk/FACT++/src/RemoteControl.h@ 14133

Last change on this file since 14133 was 14126, checked in by tbretz, 14 years ago
Removed the DimNetwork; implemented the JavaScript interpreter.
File size: 15.8 KB
Line 
1#ifndef FACT_RemoteControl
2#define FACT_RemoteControl
3
4// **************************************************************************
5/** @class RemoteControlImp
6
7@brief This implements the basic functions of a remote control via dim
8
9Through a ServiceList object this object subscribes to all available
10SERVICE_LISTs in the dim network. This allows to keep an up-to-date
11list of all servers and services. Its ProcessCommand member function
12allows to emit commands according to the services found in the network.
13Its infoHandler() is called as an update notifier from the ClientList
14object.
15
16**/
17// **************************************************************************
18#include <string>
19
20using namespace std;
21
22class RemoteControlImp
23{
24protected:
25 std::ostream &lout; /// Output stream for local synchrounous output
26
27 std::string fCurrentServer; /// The server to which we currently cd'ed
28
29protected:
30 // Redirect asynchronous output to the output window
31 RemoteControlImp(std::ostream &, std::ostream &in) : lout(in)
32 {
33 }
34 virtual ~RemoteControlImp() { }
35 bool ProcessCommand(const std::string &str);
36
37 virtual bool HasServer(const std::string &) { return false; }
38 virtual bool SendDimCommand(ostream &, std::string &, const std::string &) { return false; }
39};
40
41// **************************************************************************
42/** @class RemoteControl
43
44@brief Implements a remote control based on a Readline class for the dim network
45
46This template implements all functions which overwrite any function from the
47Readline class. Since several derivatives of the Readline class implement
48different kind of Readline access, this class can be derived by any of them
49due to its template argument. However, the normal case will be
50deriving it from either Console or Shell.
51
52@tparam T
53 The base class for RemoteControl. Either Readlien or a class
54 deriving from it. This is usually either Console or Shell.
55
56**/
57// **************************************************************************
58#include "StateMachineDimControl.h"
59
60#include "InterpreterV8.h"
61#include "ReadlineColor.h"
62#include "Event.h"
63#include "tools.h"
64
65template <class T>
66class RemoteControl : public T, public RemoteControlImp, public InterpreterV8
67{
68protected:
69 StateMachineDimControl *fImp;
70
71 void SetSection(int s) { if (fImp) fImp->Write(Time(), "", s); }
72
73 int Write(const Time &time, const std::string &txt, int qos=MessageImp::kMessage)
74 {
75 if (!fImp)
76 return 0;
77 return fImp ? fImp->Write(time, txt, qos) : MessageImp::Write(time, txt, qos);
78 }
79
80 void exitHandler(int code) { if (dynamic_cast<MainImp*>(fImp)) dynamic_cast<MainImp*>(fImp)->Stop(code); else exit(code); }
81
82 // ==================== Readline tab-completion =====================
83
84 static void append(std::string &str)
85 {
86 str.append("/");
87 }
88 static void chop(std::string &str)
89 {
90 const size_t p = str.find_first_of('/');
91 if (p!=string::npos)
92 str = str.substr(p+1);
93 }
94
95 // This funtion defines which generator should be called.
96 // If it returns 0 the standard reaqdline generator are called.
97 // Otherwise set the right generator with rl_completion_matches.
98 char **Completion(const char *text, int start, int)
99 {
100 // Get the whole buffer before the tab-position
101 const string b = string(T::GetBuffer());
102 const string s = b.substr(0, start);
103 const string l = Tools::Trim(s.c_str());
104 if (l.empty())
105 {
106 if (fCurrentServer.empty())
107 {
108 const size_t p1 = b.find_first_of(' ');
109 const size_t p2 = b.find_first_of('/');
110
111 if (p1==string::npos && p2!=string::npos)
112 return T::Complete(GetCommandList(), text);
113
114 std::vector<std::string> v = GetServerList();
115 for_each(v.begin(), v.end(), RemoteControl::append);
116 return T::Complete(v, text);
117 }
118 else
119 {
120 std::vector<std::string> v = GetCommandList(fCurrentServer);
121 for_each(v.begin(), v.end(), RemoteControl::chop);
122 return T::Complete(v, text);
123 }
124 }
125 return T::Complete(GetCommandList(l), text);
126 }
127
128 void EventHook()
129 {
130 if (fImp && !fImp->HasServer(fCurrentServer))
131 fCurrentServer = "";
132
133 T::EventHook();
134 }
135
136 // ===== Interface to access the DIM network through the StateMachine ====
137
138 bool HasServer(const std::string &server) { return fImp ? fImp->HasServer(server) : false; }
139 vector<string> GetServerList() const { return fImp ? fImp->GetServerList() : vector<string>(); }
140 vector<string> GetCommandList(const string &server) const { return fImp ? fImp->GetCommandList(server) : vector<string>(); }
141 vector<string> GetCommandList() const { return fImp ? fImp->GetCommandList() : vector<string>(); }
142 int PrintStates(std::ostream &out, const std::string &serv="") const { return fImp ? fImp->PrintStates(out, serv) : 0; }
143 int PrintDescription(std::ostream &out, bool iscmd, const std::string &serv="", const std::string &service="") const
144 { return fImp ? fImp->PrintDescription(out, iscmd, serv, service) : 0; }
145 bool SendDimCommand(ostream &out, std::string &server, const std::string &str)
146 {
147 try
148 {
149 if (fImp)
150 fImp->SendDimCommand(server, str, out);
151 //lout << kGreen << "Command emitted successfully to " << server << "." << endl;
152 return true;
153 }
154 catch (const runtime_error &e)
155 {
156 lout << kRed << e.what() << endl;
157 return false;
158 }
159 }
160
161 // ============ Pseudo-callback interface for the JavaScrip engine =======
162
163 virtual void JsLoad(const std::string &) { SetSection(-2); }
164 virtual void JsStart(const std::string &) { SetSection(-1); }
165 virtual void JsEnd(const std::string &) { UnsubscribeAll(); SetSection(-3); }
166 virtual bool JsSend(const std::string &str) { return ProcessCommand(str); }
167 virtual void JsPrint(const std::string &msg) { if (fImp) fImp->Comment(msg.empty()?" ":msg); }
168 virtual void JsException(const std::string &str) { if (fImp) fImp->Error(str.empty()?" ":str); }
169
170 pair<int32_t, string> JsState(const std::string &server)
171 {
172 return fImp ? fImp->GetServerState(server) : make_pair(-256, string());
173 }
174
175 void JsSleep(uint32_t ms)
176 {
177 const Time timeout = Time()+boost::posix_time::millisec(ms==0?1:ms);
178
179 T::Lock();
180
181 while (timeout>Time() && !T::IsScriptStopped())
182 usleep(1);
183
184 T::Unlock();
185 }
186
187 int JsWait(const string &server, int32_t state, uint32_t ms)
188 {
189 if (!fImp)
190 {
191 lout << kRed << "RemoteControl class not fully initialized." << endl;
192 T::StopScript();
193 return -1;
194 }
195
196 if (!HasServer(server))
197 {
198 lout << kRed << "Server '" << server << "' not found." << endl;
199 T::StopScript();
200 return -1;
201 }
202
203 T::Lock();
204
205 const Time timeout = ms<=0 ? Time(Time::none) : Time()+boost::posix_time::millisec(ms);
206
207 int rc = 0;
208 while (timeout>Time() && !T::IsScriptStopped())
209 {
210 const pair<int32_t, string> st = fImp->GetServerState(server);
211 if (st.first==-256)
212 {
213 lout << kRed << "Server '" << server << "' disconnected." << endl;
214 T::StopScript();
215 return -1;
216 }
217 if (st.first==state)
218 {
219 rc = 1;
220 break;
221 }
222
223 usleep(1);
224 }
225 T::Unlock();
226
227 return rc;
228 }
229
230 vector<Description> JsDescription(const string &service)
231 {
232 return fImp ? fImp->GetDescription(service) : vector<Description>();
233 }
234
235 // Keep a copy of the data for access by V8
236 map<string, EventImp *> fEvents; // For unsibscription
237 map<string, pair<uint64_t, Event>> fData;
238
239 std::mutex fMutex;
240
241 pair<uint64_t, EventImp *> JsGetEvent(const std::string &service)
242 {
243 const lock_guard<mutex> lock(fMutex);
244
245 const auto it = fData.find(service);
246
247 return it==fData.end() ? make_pair(uint64_t(0), (Event*)0) : make_pair(it->second.first, &it->second.second);
248 }
249
250 void Handle(const EventImp &evt, const string &service)
251 {
252 const lock_guard<mutex> lock(fMutex);
253
254 const auto it = fData.find(service);
255 if (it==fData.end())
256 fData[service] = make_pair(0, static_cast<Event>(evt));
257 else
258 {
259 it->second.first++;
260 it->second.second = static_cast<Event>(evt);
261 }
262 }
263
264 void *JsSubscribe(const std::string &service)
265 {
266 if (!fImp)
267 return 0;
268
269 // Do not subscribe twice
270 if (fEvents.find(service)!=fEvents.end())
271 return 0;
272
273 return fEvents[service] = &fImp->Subscribe(service)(fImp->Wrap(bind(&RemoteControl<T>::Handle, this, placeholders::_1, service)));
274 }
275
276 bool JsUnsubscribe(const std::string &service)
277 {
278 if (!fImp)
279 return false;
280
281 const auto it = fEvents.find(service);
282 if (it==fEvents.end())
283 return false;
284
285 fImp->Unsubscribe(it->second);
286 fEvents.erase(it);
287
288 return true;
289 }
290
291 void UnsubscribeAll()
292 {
293 for (auto it=fEvents.begin(); it!=fEvents.end(); it++)
294 fImp->Unsubscribe(it->second);
295 fEvents.clear();
296 }
297
298 // ===========================================================================
299
300
301public:
302 // Redirect asynchronous output to the output window
303 RemoteControl(const char *name) : T(name),
304 RemoteControlImp(T::GetStreamOut(), T::GetStreamIn()), fImp(0)
305 {
306 }
307
308 bool PrintGeneralHelp()
309 {
310 T::PrintGeneralHelp();
311 lout << " " << kUnderline << "Specific commands:\n";
312 lout << kBold << " h,help <arg> " << kReset << "List help text for given server or command.\n";
313 lout << kBold << " svc,services " << kReset << "List all services in the network.\n";
314 lout << kBold << " st,states " << kReset << "List all states in the network.\n";
315 lout << kBold << " > <text> " << kReset << "Echo <text> to the output stream\n";
316 lout << kBold << " .s " << kReset << "Wait for the state-machine to change to the given state.\n";
317 lout << " " " .s <server> [<state> [<timeout> [<label>]]]\n";
318 lout << " " "<server> The server for which state to wait (e.g. FTM_CONTROL)\n";
319 lout << " " "<state> The state id (see 'states') for which to wait (e.g. 3)\n";
320 lout << " " "<imeout> A timeout in millisenconds how long to wait (e.g. 500)\n";
321 lout << " " "<label> A label (number) until which everything is skipped in case of timeout\n";
322 lout << endl;
323 return true;
324 }
325
326 bool PrintCommands()
327 {
328 lout << endl << kBold << "List of commands:" << endl;
329 PrintDescription(lout, true);
330 return true;
331 }
332
333 // returns whether a command should be put into the history
334 bool Process(const std::string &str)
335 {
336 if (str.substr(0, 2)=="h " || str.substr(0, 5)=="help ")
337 {
338 const size_t p1 = str.find_first_of(' ');
339 const string svc = str.substr(p1+1);
340
341 const size_t p3 = svc.find_first_of('/');
342 const string s = svc.substr(0, p3);
343 const string c = p3==string::npos?"":svc.substr(p3+1);
344
345 lout << endl;
346 if (!fCurrentServer.empty())
347 {
348 if (PrintDescription(lout, true, fCurrentServer, svc)==0)
349 lout << " " << svc << ": <not found>" << endl;
350 }
351 else
352 {
353 if (PrintDescription(lout, true, s, c)==0)
354 lout << " <no matches found>" <<endl;
355 }
356
357 return true;
358 }
359
360 if (str.substr(0, 4)==".js ")
361 {
362 string opt(str.substr(4));
363
364 map<string,string> data = Tools::Split(opt, true);
365 if (opt.size()==0)
366 {
367 if (data.size()==0)
368 lout << kRed << "JavaScript filename missing." << endl;
369 else
370 lout << kRed << "Equal sign missing in argument '" << data.begin()->first << "'" << endl;
371
372 return true;
373 }
374
375 T::fScript = opt;
376 JsRun(opt, data);
377 return true;
378 }
379
380 if (str.substr(0, 3)==".s ")
381 {
382 istringstream in(str.substr(3));
383
384 int state=-100, ms=0;
385 string server;
386
387 in >> server >> state >> ms;
388 if (state==-100)
389 {
390 lout << kRed << "Couldn't parse state id in '" << str.substr(3) << "'" << endl;
391 return true;
392 }
393
394 const int rc = JsWait(server, state, ms);
395
396 if (rc<0 || rc==1)
397 return true;
398
399 int label = -1;
400 in >> label;
401 if (in.fail() && !in.eof())
402 {
403 lout << kRed << "Invalid label in '" << str.substr(3) << "'" << endl;
404 T::StopScript();
405 return true;
406 }
407 T::SetLabel(label);
408
409 return true;
410 }
411
412 if (str[0]=='>')
413 {
414 fImp->Comment(Tools::Trim(str.substr(1)));
415 return true;
416 }
417
418 if (ReadlineColor::Process(lout, str))
419 return true;
420
421 if (T::Process(str))
422 return true;
423
424 if (str=="services" || str=="svc")
425 {
426 PrintDescription(lout, false);
427 return true;
428 }
429
430 if (str=="states" || str=="st")
431 {
432 PrintStates(lout);
433 return true;
434 }
435
436 return !ProcessCommand(str);
437 }
438
439 void SetReceiver(StateMachineDimControl &imp) { fImp = &imp; }
440};
441
442
443
444// **************************************************************************
445/** @class RemoteStream
446
447 */
448// **************************************************************************
449#include "Console.h"
450
451class RemoteStream : public RemoteControl<ConsoleStream>
452{
453public:
454 RemoteStream(const char *name, bool null = false)
455 : RemoteControl<ConsoleStream>(name) { SetNullOutput(null); }
456};
457
458// **************************************************************************
459/** @class RemoteConsole
460
461@brief Derives the RemoteControl from Control and adds a proper prompt
462
463This is basically a RemoteControl, which derives through the template
464argument from the Console class. It enhances the functionality of
465the remote control with a proper updated prompt.
466
467 */
468// **************************************************************************
469
470class RemoteConsole : public RemoteControl<Console>
471{
472public:
473 RemoteConsole(const char *name, bool continous=false) :
474 RemoteControl<Console>(name)
475 {
476 SetContinous(continous);
477 }
478 string GetUpdatePrompt() const;
479};
480
481// **************************************************************************
482/** @class RemoteShell
483
484@brief Derives the RemoteControl from Shell and adds colored prompt
485
486This is basically a RemoteControl, which derives through the template
487argument from the Shell class. It enhances the functionality of
488the local control with a proper updated prompt.
489
490 */
491// **************************************************************************
492#include "Shell.h"
493
494class RemoteShell : public RemoteControl<Shell>
495{
496public:
497 RemoteShell(const char *name, bool = false) :
498 RemoteControl<Shell>(name)
499 {
500 }
501 string GetUpdatePrompt() const;
502};
503
504#endif
Note: See TracBrowser for help on using the repository browser.