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 void MacroRecorder::add_planned_interruption(const char *displayed_text) {
284  recording->write_planned_interruption(displayed_text);
285 }
286 
287 // -----------------------------
288 // ClientActionTracker
289 
291  bool recording = GB_read_int(gb_recording);
292  if (is_tracking() != recording) set_recording(recording);
293 }
294 
296  cat->set_tracking_according_to(gb_recording);
297 }
298 
299 void ClientActionTracker::bind_callbacks(bool install) {
301  GB_ERROR error = NULp;
303 
304  if (!gb_recording) {
305  error = GB_await_error();
306  }
307  else {
308  if (install) {
309  error = GB_add_callback(gb_recording, GB_CB_CHANGED, makeDatabaseCallback(record_state_changed_cb, this));
310  record_state_changed_cb(gb_recording, this); // call once
311  }
312  else {
313  GB_remove_callback(gb_recording, GB_CB_CHANGED, makeDatabaseCallback(record_state_changed_cb, this));
314  }
315  }
316 
317  if (error) {
318  aw_message(GBS_global_string("Failed to %s ClientActionTracker: %s", install ? "init" : "cleanup", error));
319  }
320 }
321 
322 
323 void ClientActionTracker::track_action(const char *action_id) {
324  if (!action_id) {
325  warn_unrecordable("anonymous GUI element");
326  }
327  else {
328  ma_assert(!strchr(action_id, '*'));
329  send_client_action(GBS_global_string("ACTION*%s", action_id));
330  }
331 }
332 
334  // see also recmac.cxx@AWAR_CHANGE_TRACKING
335 
336  char *svalue = awar->read_as_string();
337  if (!svalue) {
338  warn_unrecordable(GBS_global_string("change of '%s'", awar->awar_name));
339  }
340  else {
341  ma_assert(!strchr(awar->awar_name, '*'));
342  send_client_action(GBS_global_string("AWAR*%s*%s", awar->awar_name, svalue));
343  free(svalue);
344  }
345 }
346 
347 void ClientActionTracker::send_client_action(const char *action) {
348  // action is either
349  // "ACTION*<actionId>" or
350  // "AWAR*<awarName>*<awarValue>"
351 
352  // send action
353  GB_ERROR error;
354  GBDATA *gb_clientTrack = NULp;
355  {
356  error = GB_begin_transaction(get_gbmain());
357  if (!error) {
359  if (!gb_clientTrack) error = GB_await_error();
360  else {
361  const char *prev_track = GB_read_char_pntr(gb_clientTrack);
362 
363  if (!prev_track) error = GB_await_error();
364  else if (prev_track[0]) error = GBS_global_string("Cant send_client_action: have pending client action (%s)", prev_track);
365 
366  if (!error) {
367  ma_assert(!strchr(get_application_id(), '*'));
368  error = GB_write_string(gb_clientTrack, GBS_global_string("%s*%s", get_application_id(), action));
369  }
370  }
371  }
372  error = GB_end_transaction(get_gbmain(), error);
373  }
374 
375  if (!error) {
376  // wait for recorder to consume action
377  bool consumed = false;
378  int count = 0;
379  MacroTalkSleep increasing;
380 
381  while (!consumed && !error) {
382  increasing.sleep();
383  ++count;
384  if ((count%25) == 0) {
385  fprintf(stderr, "[Waiting for macro recorder to consume action tracked by %s]\n", get_application_id());
386  }
387 
388  error = GB_begin_transaction(get_gbmain());
389 
390  const char *track = GB_read_char_pntr(gb_clientTrack);
391  if (!track) error = GB_await_error();
392  else consumed = !track[0];
393 
394  error = GB_end_transaction(get_gbmain(), error);
395  }
396  }
397 
398  if (error) {
399  aw_message(GBS_global_string("Failed to record client action (Reason: %s)", error));
400  }
401 }
402 
403 void ClientActionTracker::ungrant_client_and_confirm_quit_action() {
404  // shutdown macro client
405  // - confirm action (needed in case the quit has been triggered by a macro; otherwise macro hangs forever)
406  // - unauthorize this process for macro execution
407 
409  GB_transaction ta(gb_main);
411  GB_ERROR error = NULp;
412 
413  GBDATA *gb_granted = GB_search(gb_main, remote.granted(), GB_FIND);
414  if (gb_granted) {
415  pid_t pid = getpid();
416  pid_t granted_pid = GB_read_int(gb_granted);
417 
418  if (pid == granted_pid) { // this is the client with macro execution rights
419  GBDATA *gb_action = GB_search(gb_main, remote.action(), GB_FIND);
420  if (gb_action) error = GB_write_string(gb_action, ""); // signal macro, that action was executed
421  if (!error) error = GB_write_int(gb_granted, 0); // un-authorize this process
422  }
423  }
424 
425  if (error) {
426  error = GB_set_macro_error(gb_main, GBS_global_string("error during client quit: %s", error));
427  if (error) fprintf(stderr, "Error in ungrant_client_and_confirm_quit_action: %s\n", error);
428  }
429 
430  if (is_tracking()) set_recording(false);
431 }
432 
433 // -------------------------
434 // tracker factory
435 
436 static UserActionTracker *make_macro_recording_tracker(const char *client_id, GBDATA *gb_main) {
437  // 'client_id' has to be a unique id (used to identify the program which will record/playback).
438  // If multiple programs (or multiple instances of one) use the same id, macro recording shall abort.
439  // If a program is used for different purposes by starting multiple instances (like e.g. arb_ntree),
440  // each purpose/instance should use a different 'client_id'.
441 
442  ma_assert(gb_main);
443  ma_assert(client_id && client_id[0]);
444 
445  BoundActionTracker *tracker;
446  if (GB_is_server(gb_main)) {
447  tracker = new MacroRecorder(client_id, gb_main);
448  }
449  else {
450  tracker = new ClientActionTracker(client_id, gb_main);
451  }
452  return tracker;
453 }
454 
456  return new RequiresActionTracker;
457 }
458 
459 GB_ERROR configure_macro_recording(AW_root *aw_root, const char *client_id, GBDATA *gb_main) {
460  ma_assert(aw_root);
461 
463  GB_ERROR error = NULp;
464  if (existing && existing->reconfigure(client_id, gb_main)) {
465  error = reconfigure_dbserver(client_id, gb_main);
466  }
467  else {
468  aw_root->setUserActionTracker(make_macro_recording_tracker(client_id, gb_main));
469  error = startup_dbserver(aw_root, client_id, gb_main);
470  }
471 
472  return error;
473 }
474 
477  if (tracker) tracker->release();
478 }
479 
480 bool got_macro_ability(AW_root *aw_root) {
481  // return true if aw_root has a BoundActionTracker
482  return get_active_macro_recording_tracker(aw_root);
483 }
484 
#define ma_assert(bed)
Definition: macro_gui.cxx:28
GB_ERROR GB_begin_transaction(GBDATA *gbd)
Definition: arbdb.cxx:2528
void track_action(const char *action_id)
Definition: recmac.cxx:161
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:188
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:729
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:1387
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:180
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:2561
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:436
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:203
void track_awar_change(AW_awar *awar) OVERRIDE
Definition: trackers.cxx:333
UserActionTracker * need_macro_ability()
Definition: trackers.cxx:455
void track_action(const char *action_id) OVERRIDE
Definition: trackers.cxx:238
void GBK_terminatef(const char *templat,...)
Definition: arb_msg.cxx:523
void shutdown_macro_recording(AW_root *aw_root)
Definition: trackers.cxx:475
const char * authReq() const
Definition: ad_remote.h:78
virtual void release()=0
GB_ERROR stop()
Definition: recmac.cxx:199
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:342
static AW_root * SINGLETON
Definition: aw_root.hxx:102
void add_planned_interruption(const char *displayed_text)
Definition: trackers.cxx:282
void track_action(const char *action_id) OVERRIDE
Definition: trackers.cxx:323
GB_CSTR GB_read_key_pntr(GBDATA *gbd)
Definition: arbdb.cxx:1656
void set_tracking_according_to(GBDATA *gb_recording)
Definition: trackers.cxx:290
static void add(const RootCallback &execution_done_cb)
Definition: trackers.cxx:172
bool got_macro_ability(AW_root *aw_root)
Definition: trackers.cxx:480
static void error(const char *msg)
Definition: mkptypes.cxx:96
GBDATA * GB_get_root(GBDATA *gbd)
Definition: arbdb.cxx:1740
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:295
void write_awar_change(const char *app_id, const char *awar_name, const char *content)
Definition: recmac.cxx:144
#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:1023
GB_ERROR GB_write_int(GBDATA *gbd, long i)
Definition: arbdb.cxx:1250
static void drop()
Definition: trackers.cxx:181
void set_recording(bool recording)
Definition: trackers.cxx:31
char * awar_name
Definition: aw_awar.hxx:103
void write_planned_interruption(const char *displayed_text)
Definition: recmac.cxx:153
GB_ERROR configure_macro_recording(AW_root *aw_root, const char *client_id, GBDATA *gb_main)
Definition: trackers.cxx:459
#define IF_ASSERTION_USED(x)
Definition: arb_assert.h:308
GB_ERROR close(GB_ERROR error)
Definition: arbdbpp.cxx:35
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:141
void write_action(const char *app_id, const char *action_name)
Definition: recmac.cxx:104
void aw_message(const char *msg)
Definition: AW_status.cxx:1142
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:116
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:30
GBDATA * get_gbmain()
Definition: trackers.hxx:66
GB_transaction ta(gb_var)
GB_CSTR GB_read_char_pntr(GBDATA *gbd)
Definition: arbdb.cxx:904
GBDATA * gb_main
Definition: adname.cxx:32
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