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

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