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

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