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

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