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

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