ARB
trackers.cxx
Go to the documentation of this file.
1 // ============================================================= //
2 // //
3 // File : trackers.cxx //
4 // Purpose : //
5 // //
6 // Coded by Ralf Westram (coder@reallysoft.de) in March 2013 //
7 // Institute of Microbiology (Technical University Munich) //
8 // http://www.arb-home.de/ //
9 // //
10 // ============================================================= //
11 
12 #include "trackers.hxx"
13 #include "macros.hxx"
14 #include "recmac.hxx"
15 #include "dbserver.hxx"
16 
17 #include <aw_msg.hxx>
18 #include <arb_strarray.h>
19 #include <arb_sleep.h>
20 #include <ad_remote.h>
21 #include <unistd.h>
22 #include <ad_cb.h>
23 
24 bool BoundActionTracker::reconfigure(const char *application_id, GBDATA *IF_ASSERTION_USED(gb_main)) {
25  ma_assert(gb_main == gbmain);
26  ma_assert(strcmp(id, "ARB_IMPORT") == 0); // currently only ARB_IMPORT-tracker gets reconfigured
27  freedup(id, application_id);
28  return true;
29 }
30 
31 void BoundActionTracker::set_recording(bool recording) {
33  {
36  GBDATA *gb_recAuth = GB_searchOrCreate_int(get_gbmain(), remote.recAuth(), 0);
37 
38  if (!gb_recAuth) {
39  error = GB_await_error();
40  }
41  else {
42  pid_t pid = getpid();
43  pid_t recPid = GB_read_int(gb_recAuth);
44 
45  if (recording) {
46  if (recPid == 0) {
47  error = GB_write_int(gb_recAuth, pid); // allocate permission to record
48  }
49  else {
50  error = GBS_global_string("Detected two recording clients with id '%s'", get_application_id());
51  }
52  }
53  else {
54  if (recPid == pid) { // this is the authorized process
55  error = GB_write_int(gb_recAuth, 0); // clear permission
56  }
57  }
58  }
59 
60  if (error) {
61  error = GB_set_macro_error(get_gbmain(), error);
62  if (error) GBK_terminatef("Failed to set macro-error: %s", error);
63  }
64  }
65  set_tracking(recording);
66 }
67 
68 static GB_ERROR announce_recording(GBDATA *gb_main, int record) {
69  GB_transaction ta(gb_main);
70  GBDATA *gb_recording = GB_searchOrCreate_int(gb_main, MACRO_TRIGGER_RECORDING, record);
71  return gb_recording ? GB_write_int(gb_recording, record) : GB_await_error();
72 }
73 
74 GB_ERROR MacroRecorder::start_recording(const char *file, const char *stop_action_name, bool expand_existing) {
76  if (is_tracking()) error = "Already recording macro";
77  else {
78  recording = new RecordingMacro(file, get_application_id(), stop_action_name, expand_existing);
79  set_recording(true);
80 
81  error = recording->has_error();
82  if (!error) error = announce_recording(get_gbmain(), 1);
83  if (error) {
84  GB_ERROR stop_error = stop_recording();
85  if (stop_error) fprintf(stderr, "Error while stopping macro recording: %s\n", stop_error);
86  }
87  }
88  return error;
89 }
90 
93  if (!is_tracking()) {
94  error = "Not recording macro";
95  }
96  else {
97  error = recording->stop();
98 
99  delete recording;
100  recording = NULp;
101  set_recording(false);
102 
103  GB_ERROR ann_error = announce_recording(get_gbmain(), 0);
104  if (error) {
105  if (ann_error) fprintf(stderr, "Error in announce_recording: %s\n", ann_error);
106  }
107  else {
108  error = ann_error;
109  }
110  }
111  return error;
112 }
113 
114 static void getKnownMacroClients(ConstStrArray& clientNames, GBDATA *gb_main) {
115  GB_transaction ta(gb_main);
116 
117  GBDATA *gb_remote = GB_search(gb_main, REMOTE_BASE, GB_FIND);
118  GBDATA *gb_control = GB_search(gb_main, MACRO_TRIGGER_CONTAINER, GB_FIND);
119 
120  clientNames.erase();
121  if (gb_remote) {
122  for (GBDATA *gb_client = GB_child(gb_remote); gb_client; gb_client = GB_nextChild(gb_client)) {
123  if (gb_client != gb_control) {
124  const char *client_id = GB_read_key_pntr(gb_client);
125  clientNames.put(client_id);
126  }
127  }
128  }
129 }
130 __ATTR__USERESULT inline GB_ERROR setIntEntryToZero(GBDATA *gb_main, const char *entryPath) {
131  GBDATA *gbe = GB_search(gb_main, entryPath, GB_INT);
132  return gbe ? GB_write_int(gbe, 0) : GB_await_error();
133 }
134 
136  // clear all granted client authorizations
137  GB_transaction ta(gb_main);
138  GB_ERROR error = NULp;
139 
140  ConstStrArray clientNames;
141  getKnownMacroClients(clientNames, gb_main);
142 
143  for (size_t i = 0; i<clientNames.size() && !error; ++i) {
144  remote_awars remote(clientNames[i]);
145  error = setIntEntryToZero(gb_main, remote.authReq());
146  if (!error) error = setIntEntryToZero(gb_main, remote.authAck());
147  if (!error) error = setIntEntryToZero(gb_main, remote.granted());
148  // if (!error) error = setIntEntryToZero(gb_main, remote.recAuth()); // @@@ clear elsewhere
149  }
150  if (error) error = GBS_global_string("error in clearMacroExecutionAuthorization: %s", error);
151  return error;
152 }
153 
154 class ExecutingMacro : virtual Noncopyable {
155  RootCallback done_cb;
156 
157  ExecutingMacro *next;
158  static ExecutingMacro *head;
159 
160  ExecutingMacro(const RootCallback& execution_done_cb)
161  : done_cb(execution_done_cb),
162  next(head)
163  {
164  head = this;
165  }
166 
167  void call() const { done_cb(AW_root::SINGLETON); }
168  void destroy() { head = next; delete this; }
169 
170 public:
171 
172  static void add(const RootCallback& execution_done_cb) { new ExecutingMacro(execution_done_cb); }
173  static bool done() {
174  // returns true if the last macro (of all recursively called macros) terminates
175  if (head) {
176  head->call();
177  head->destroy();
178  }
179  return !head;
180  }
181  static void drop() {
182  if (head) head->destroy();
183  }
184 };
185 
186 ExecutingMacro *ExecutingMacro::head = NULp;
187 
188 static void macro_terminated(GBDATA *gb_terminated, GB_CB_TYPE IF_ASSERTION_USED(cb_type)) {
189  ma_assert(cb_type == GB_CB_CHANGED);
190  fprintf(stderr, "macro_terminated called\n");
191  bool allMacrosTerminated = ExecutingMacro::done();
192  if (allMacrosTerminated) {
193  fprintf(stderr, "macro_terminated: allMacrosTerminated\n");
194  GBDATA *gb_main = GB_get_root(gb_terminated);
196  aw_message_if(error);
197 
198  // check for global macro error
199  GB_ERROR macro_error = GB_get_macro_error(gb_main);
200  if (macro_error) {
201  aw_message_if(macro_error);
202  aw_message("Warning: macro terminated (somewhere in the middle)");
203 
204  GB_ERROR clr_error = GB_clear_macro_error(gb_main);
205  if (clr_error) fprintf(stderr, "Warning: failed to clear macro error (Reason: %s)\n", clr_error);
206  }
207  }
208 }
209 
210 GB_ERROR MacroRecorder::execute(const char *macroFile, bool loop_marked, const RootCallback& execution_done_cb) {
211  GB_ERROR error = NULp;
212  {
214  GB_transaction ta(gb_main);
215 
216  GBDATA *gb_term = GB_search(gb_main, MACRO_TRIGGER_TERMINATED, GB_FIND);
217  if (!gb_term) {
218  gb_term = GB_search(gb_main, MACRO_TRIGGER_TERMINATED, GB_INT);
219  if (!gb_term) {
220  error = GB_await_error();
221  }
222  else {
223  GB_add_callback(gb_term, GB_CB_CHANGED, makeDatabaseCallback(macro_terminated));
224  }
225  }
226  error = ta.close(error);
227  }
228 
229  if (!error) {
230  ExecutingMacro::add(execution_done_cb);
231  error = GBT_macro_execute(macroFile, loop_marked, true);
232  if (error) ExecutingMacro::drop(); // avoid double free
233  }
234 
235  return error;
236 }
237 
238 void MacroRecorder::track_action(const char *action_id) {
240  recording->track_action(action_id);
241 }
242 
245  recording->track_awar_change(awar);
246 }
247 
249  ma_assert(tracked && tracked[0]);
250 
251  GB_ERROR error = NULp;
252  if (tracked && tracked[0]) {
253  char *saveptr = NULp;
254  const char *app_id = strtok_r(tracked, "*", &saveptr);
255  const char *cmd = strtok_r(NULp, "*", &saveptr);
256  char *rest = strtok_r(NULp, "", &saveptr);
257 
258  if (recording) {
259  if (strcmp(cmd, "AWAR") == 0) {
260  const char *awar_name = strtok_r(rest, "*", &saveptr);
261  const char *content = strtok_r(NULp, "", &saveptr);
262 
263  if (!content) content = "";
264 
265  recording->write_awar_change(app_id, awar_name, content);
266  }
267  else if (strcmp(cmd, "ACTION") == 0) {
268  recording->write_action(app_id, rest);
269  }
270  else {
271  error = GBS_global_string("Unknown client action '%s'", cmd);
272  }
273  }
274  else {
275  fprintf(stderr, "Warning: tracked action '%s' from client '%s' (dropped because not recording)\n", cmd, app_id);
276  }
277  }
278 
279  return error;
280 }
281 
282 // -----------------------------
283 // ClientActionTracker
284 
286  bool recording = GB_read_int(gb_recording);
287  if (is_tracking() != recording) set_recording(recording);
288 }
289 
291  cat->set_tracking_according_to(gb_recording);
292 }
293 
294 void ClientActionTracker::bind_callbacks(bool install) {
296  GB_ERROR error = NULp;
298 
299  if (!gb_recording) {
300  error = GB_await_error();
301  }
302  else {
303  if (install) {
304  error = GB_add_callback(gb_recording, GB_CB_CHANGED, makeDatabaseCallback(record_state_changed_cb, this));
305  record_state_changed_cb(gb_recording, this); // call once
306  }
307  else {
308  GB_remove_callback(gb_recording, GB_CB_CHANGED, makeDatabaseCallback(record_state_changed_cb, this));
309  }
310  }
311 
312  if (error) {
313  aw_message(GBS_global_string("Failed to %s ClientActionTracker: %s", install ? "init" : "cleanup", error));
314  }
315 }
316 
317 
318 void ClientActionTracker::track_action(const char *action_id) {
319  if (!action_id) {
320  warn_unrecordable("anonymous GUI element");
321  }
322  else {
323  ma_assert(!strchr(action_id, '*'));
324  send_client_action(GBS_global_string("ACTION*%s", action_id));
325  }
326 }
327 
329  // see also recmac.cxx@AWAR_CHANGE_TRACKING
330 
331  char *svalue = awar->read_as_string();
332  if (!svalue) {
333  warn_unrecordable(GBS_global_string("change of '%s'", awar->awar_name));
334  }
335  else {
336  ma_assert(!strchr(awar->awar_name, '*'));
337  send_client_action(GBS_global_string("AWAR*%s*%s", awar->awar_name, svalue));
338  free(svalue);
339  }
340 }
341 
342 void ClientActionTracker::send_client_action(const char *action) {
343  // action is either
344  // "ACTION*<actionId>" or
345  // "AWAR*<awarName>*<awarValue>"
346 
347  // send action
348  GB_ERROR error;
349  GBDATA *gb_clientTrack = NULp;
350  {
351  error = GB_begin_transaction(get_gbmain());
352  if (!error) {
354  if (!gb_clientTrack) error = GB_await_error();
355  else {
356  const char *prev_track = GB_read_char_pntr(gb_clientTrack);
357 
358  if (!prev_track) error = GB_await_error();
359  else if (prev_track[0]) error = GBS_global_string("Cant send_client_action: have pending client action (%s)", prev_track);
360 
361  if (!error) {
362  ma_assert(!strchr(get_application_id(), '*'));
363  error = GB_write_string(gb_clientTrack, GBS_global_string("%s*%s", get_application_id(), action));
364  }
365  }
366  }
367  error = GB_end_transaction(get_gbmain(), error);
368  }
369 
370  if (!error) {
371  // wait for recorder to consume action
372  bool consumed = false;
373  int count = 0;
374  MacroTalkSleep increasing;
375 
376  while (!consumed && !error) {
377  increasing.sleep();
378  ++count;
379  if ((count%25) == 0) {
380  fprintf(stderr, "[Waiting for macro recorder to consume action tracked by %s]\n", get_application_id());
381  }
382 
383  error = GB_begin_transaction(get_gbmain());
384 
385  const char *track = GB_read_char_pntr(gb_clientTrack);
386  if (!track) error = GB_await_error();
387  else consumed = !track[0];
388 
389  error = GB_end_transaction(get_gbmain(), error);
390  }
391  }
392 
393  if (error) {
394  aw_message(GBS_global_string("Failed to record client action (Reason: %s)", error));
395  }
396 }
397 
398 void ClientActionTracker::ungrant_client_and_confirm_quit_action() {
399  // shutdown macro client
400  // - confirm action (needed in case the quit has been triggered by a macro; otherwise macro hangs forever)
401  // - unauthorize this process for macro execution
402 
404  GB_transaction ta(gb_main);
406  GB_ERROR error = NULp;
407 
408  GBDATA *gb_granted = GB_search(gb_main, remote.granted(), GB_FIND);
409  if (gb_granted) {
410  pid_t pid = getpid();
411  pid_t granted_pid = GB_read_int(gb_granted);
412 
413  if (pid == granted_pid) { // this is the client with macro execution rights
414  GBDATA *gb_action = GB_search(gb_main, remote.action(), GB_FIND);
415  if (gb_action) error = GB_write_string(gb_action, ""); // signal macro, that action was executed
416  if (!error) error = GB_write_int(gb_granted, 0); // un-authorize this process
417  }
418  }
419 
420  if (error) {
421  error = GB_set_macro_error(gb_main, GBS_global_string("error during client quit: %s", error));
422  if (error) fprintf(stderr, "Error in ungrant_client_and_confirm_quit_action: %s\n", error);
423  }
424 
425  if (is_tracking()) set_recording(false);
426 }
427 
428 // -------------------------
429 // tracker factory
430 
431 static UserActionTracker *make_macro_recording_tracker(const char *client_id, GBDATA *gb_main) {
432  // 'client_id' has to be a unique id (used to identify the program which will record/playback).
433  // If multiple programs (or multiple instances of one) use the same id, macro recording shall abort.
434  // If a program is used for different purposes by starting multiple instances (like e.g. arb_ntree),
435  // each purpose/instance should use a different 'client_id'.
436 
437  ma_assert(gb_main);
438  ma_assert(client_id && client_id[0]);
439 
440  BoundActionTracker *tracker;
441  if (GB_is_server(gb_main)) {
442  tracker = new MacroRecorder(client_id, gb_main);
443  }
444  else {
445  tracker = new ClientActionTracker(client_id, gb_main);
446  }
447  return tracker;
448 }
449 
451  return new RequiresActionTracker;
452 }
453 
454 GB_ERROR configure_macro_recording(AW_root *aw_root, const char *client_id, GBDATA *gb_main) {
455  ma_assert(aw_root);
456 
458  GB_ERROR error = NULp;
459  if (existing && existing->reconfigure(client_id, gb_main)) {
460  error = reconfigure_dbserver(client_id, gb_main);
461  }
462  else {
463  aw_root->setUserActionTracker(make_macro_recording_tracker(client_id, gb_main));
464  error = startup_dbserver(aw_root, client_id, gb_main);
465  }
466 
467  return error;
468 }
469 
472  if (tracker) tracker->release();
473 }
474 
475 bool got_macro_ability(AW_root *aw_root) {
476  // return true if aw_root has a BoundActionTracker
477  return get_active_macro_recording_tracker(aw_root);
478 }
479 
#define ma_assert(bed)
Definition: macro_gui.cxx:27
GB_ERROR GB_begin_transaction(GBDATA *gbd)
Definition: arbdb.cxx:2516
void track_action(const char *action_id)
Definition: recmac.cxx:153
const char * GB_ERROR
Definition: arb_core.h:25
GB_ERROR start_recording(const char *file, const char *stop_action_name, bool expand_existing)
Definition: trackers.cxx:74
#define MACRO_TRIGGER_TRACKED
Definition: arbdbt.h:27
void put(const char *elem)
Definition: arb_strarray.h:199
size_t size() const
Definition: arb_strarray.h:85
GB_ERROR GB_set_macro_error(GBDATA *gb_main, const char *curr_error)
Definition: adtools.cxx:670
long GB_read_int(GBDATA *gbd)
Definition: arbdb.cxx:723
GBDATA * GB_child(GBDATA *father)
Definition: adquery.cxx:322
static void macro_terminated(GBDATA *gb_terminated, GB_CB_TYPE cb_type)
Definition: trackers.cxx:188
bool is_tracking() const
Definition: aw_root.hxx:70
bool GB_is_server(GBDATA *gbd)
Definition: adcomm.cxx:1697
const char * granted() const
Definition: ad_remote.h:80
GB_ERROR GB_write_string(GBDATA *gbd, const char *s)
Definition: arbdb.cxx:1385
GBDATA * GB_searchOrCreate_string(GBDATA *gb_container, const char *fieldpath, const char *default_value)
Definition: adquery.cxx:546
GB_ERROR GB_add_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback &dbcb)
Definition: ad_cb.cxx:356
void track_awar_change(AW_awar *awar)
Definition: recmac.cxx:166
GB_ERROR GBT_macro_execute(const char *macro_name, bool loop_marked, bool run_async)
Definition: adtools.cxx:919
GB_ERROR GB_end_transaction(GBDATA *gbd, GB_ERROR error)
Definition: arbdb.cxx:2549
GB_ERROR execute(const char *macroFile, bool loop_marked, const RootCallback &execution_done_cb)
Definition: trackers.cxx:210
void sleep()
Definition: arb_sleep.h:94
static UserActionTracker * make_macro_recording_tracker(const char *client_id, GBDATA *gb_main)
Definition: trackers.cxx:431
const char * authAck() const
Definition: ad_remote.h:79
#define MACRO_TRIGGER_TERMINATED
Definition: arbdbt.h:24
void track_awar_change(AW_awar *awar) OVERRIDE
Definition: trackers.cxx:243
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:204
void track_awar_change(AW_awar *awar) OVERRIDE
Definition: trackers.cxx:328
UserActionTracker * need_macro_ability()
Definition: trackers.cxx:450
void track_action(const char *action_id) OVERRIDE
Definition: trackers.cxx:238
void GBK_terminatef(const char *templat,...)
Definition: arb_msg.cxx:477
void shutdown_macro_recording(AW_root *aw_root)
Definition: trackers.cxx:470
const char * authReq() const
Definition: ad_remote.h:78
virtual void release()=0
GB_ERROR stop()
Definition: recmac.cxx:181
void setUserActionTracker(UserActionTracker *user_tracker)
Definition: AW_root.cxx:210
GB_ERROR reconfigure_dbserver(const char *application_id, GBDATA *gb_main)
Definition: dbserver.cxx:292
GB_ERROR GB_await_error()
Definition: arb_msg.cxx:353
static AW_root * SINGLETON
Definition: aw_root.hxx:102
void track_action(const char *action_id) OVERRIDE
Definition: trackers.cxx:318
GB_CSTR GB_read_key_pntr(GBDATA *gbd)
Definition: arbdb.cxx:1654
void set_tracking_according_to(GBDATA *gb_recording)
Definition: trackers.cxx:285
static void add(const RootCallback &execution_done_cb)
Definition: trackers.cxx:172
bool got_macro_ability(AW_root *aw_root)
Definition: trackers.cxx:475
static void error(const char *msg)
Definition: mkptypes.cxx:96
GBDATA * GB_get_root(GBDATA *gbd)
Definition: arbdb.cxx:1738
GBDATA * GB_searchOrCreate_int(GBDATA *gb_container, const char *fieldpath, long default_value)
Definition: adquery.cxx:569
char * read_as_string() const
static __ATTR__USERESULT GB_ERROR clearMacroExecutionAuthorization(GBDATA *gb_main)
Definition: trackers.cxx:135
bool reconfigure(const char *application_id, GBDATA *gb_main)
Definition: trackers.cxx:24
static void record_state_changed_cb(GBDATA *gb_recording, ClientActionTracker *cat)
Definition: trackers.cxx:290
void write_awar_change(const char *app_id, const char *awar_name, const char *content)
Definition: recmac.cxx:143
#define REMOTE_BASE
Definition: arbdbt.h:22
static GB_ERROR announce_recording(GBDATA *gb_main, int record)
Definition: trackers.cxx:68
GB_ERROR startup_dbserver(AW_root *aw_root, const char *application_id, GBDATA *gb_main)
Definition: dbserver.cxx:252
static void getKnownMacroClients(ConstStrArray &clientNames, GBDATA *gb_main)
Definition: trackers.cxx:114
Definition: arbdb.h:86
#define MACRO_TRIGGER_CONTAINER
Definition: arbdbt.h:23
static char * cat(char *toBuf, const char *s1, const char *s2)
Definition: ED4_root.cxx:1037
GB_ERROR GB_write_int(GBDATA *gbd, long i)
Definition: arbdb.cxx:1244
static void drop()
Definition: trackers.cxx:181
void set_recording(bool recording)
Definition: trackers.cxx:31
char * awar_name
Definition: aw_awar.hxx:103
GB_ERROR configure_macro_recording(AW_root *aw_root, const char *client_id, GBDATA *gb_main)
Definition: trackers.cxx:454
#define IF_ASSERTION_USED(x)
Definition: arb_assert.h:308
GB_ERROR close(GB_ERROR error)
Definition: arbdbpp.cxx:32
static bool done()
Definition: trackers.cxx:173
const char * get_application_id() const
Definition: trackers.hxx:67
GB_ERROR handle_tracked_client_action(char *&tracked)
Definition: trackers.cxx:248
#define __ATTR__USERESULT
Definition: attributes.h:58
#define MACRO_TRIGGER_RECORDING
Definition: arbdbt.h:25
void GB_remove_callback(GBDATA *gbd, GB_CB_TYPE type, const DatabaseCallback &dbcb)
Definition: ad_cb.cxx:360
BoundActionTracker * get_active_macro_recording_tracker(AW_root *aw_root)
Definition: trackers.hxx:139
void write_action(const char *app_id, const char *action_name)
Definition: recmac.cxx:103
void aw_message(const char *msg)
Definition: AW_status.cxx:932
const char * recAuth() const
Definition: ad_remote.h:82
GB_ERROR GB_clear_macro_error(GBDATA *gb_main)
Definition: adtools.cxx:699
#define NULp
Definition: cxxforward.h:97
GB_ERROR stop_recording()
Definition: trackers.cxx:91
__ATTR__USERESULT GB_ERROR setIntEntryToZero(GBDATA *gb_main, const char *entryPath)
Definition: trackers.cxx:130
GB_ERROR has_error() const
Definition: recmac.hxx:52
GBDATA * GB_nextChild(GBDATA *child)
Definition: adquery.cxx:326
void warn_unrecordable(const char *what)
Definition: recmac.cxx:29
GBDATA * get_gbmain()
Definition: trackers.hxx:66
GB_transaction ta(gb_var)
GB_CSTR GB_read_char_pntr(GBDATA *gbd)
Definition: arbdb.cxx:898
GBDATA * gb_main
Definition: adname.cxx:33
GBDATA * GB_search(GBDATA *gbd, const char *fieldpath, GB_TYPES create)
Definition: adquery.cxx:531
GB_CB_TYPE
Definition: arbdb_base.h:46
GB_ERROR GB_get_macro_error(GBDATA *gb_main)
Definition: adtools.cxx:687
void aw_message_if(GB_ERROR error)
Definition: aw_msg.hxx:21
Definition: arbdb.h:66