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

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