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

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