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

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