| 1 | // ============================================================= // |
|---|
| 2 | // // |
|---|
| 3 | // File : dbserver.cxx // |
|---|
| 4 | // Purpose : // |
|---|
| 5 | // // |
|---|
| 6 | // Coded by Ralf Westram (coder@reallysoft.de) in April 2013 // |
|---|
| 7 | // Institute of Microbiology (Technical University Munich) // |
|---|
| 8 | // http://www.arb-home.de/ // |
|---|
| 9 | // // |
|---|
| 10 | // ============================================================= // |
|---|
| 11 | |
|---|
| 12 | #include "trackers.hxx" |
|---|
| 13 | #include "dbserver.hxx" |
|---|
| 14 | #include "macros.hxx" |
|---|
| 15 | |
|---|
| 16 | #include <arbdbt.h> |
|---|
| 17 | #include <ad_remote.h> |
|---|
| 18 | |
|---|
| 19 | #include <aw_awar.hxx> |
|---|
| 20 | #include <aw_msg.hxx> |
|---|
| 21 | #include <aw_window.hxx> |
|---|
| 22 | |
|---|
| 23 | #include <unistd.h> |
|---|
| 24 | |
|---|
| 25 | #if defined(DEBUG) |
|---|
| 26 | // # define DUMP_REMOTE_ACTIONS |
|---|
| 27 | // # define DUMP_AUTHORIZATION // see also ../../ARBDB/adtools.cxx@DUMP_AUTH_HANDSHAKE |
|---|
| 28 | #endif |
|---|
| 29 | |
|---|
| 30 | #if defined(DUMP_REMOTE_ACTIONS) |
|---|
| 31 | # define IF_DUMP_ACTION(cmd) cmd |
|---|
| 32 | #else |
|---|
| 33 | # define IF_DUMP_ACTION(cmd) |
|---|
| 34 | #endif |
|---|
| 35 | |
|---|
| 36 | #if defined(DUMP_AUTHORIZATION) |
|---|
| 37 | # define IF_DUMP_AUTH(cmd) cmd |
|---|
| 38 | #else |
|---|
| 39 | # define IF_DUMP_AUTH(cmd) |
|---|
| 40 | #endif |
|---|
| 41 | |
|---|
| 42 | |
|---|
| 43 | |
|---|
| 44 | #define ARB_SERVE_DB_TIMER 50 // ms |
|---|
| 45 | #define ARB_CHECK_DB_TIMER 200 // ms |
|---|
| 46 | |
|---|
| 47 | struct db_interrupt_data : virtual Noncopyable { |
|---|
| 48 | remote_awars remote; |
|---|
| 49 | GBDATA *gb_main; |
|---|
| 50 | |
|---|
| 51 | db_interrupt_data(GBDATA *gb_main_, const char *application_id) |
|---|
| 52 | : remote(application_id), |
|---|
| 53 | gb_main(gb_main_) |
|---|
| 54 | {} |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | GB_ERROR reconfigure(GBDATA *gb_main_, const char *application_id) { |
|---|
| 58 | GB_ERROR error = NULp; |
|---|
| 59 | if (gb_main_ != gb_main) { |
|---|
| 60 | error = "Attempt to reconfigure database interrupt with changed database"; |
|---|
| 61 | } |
|---|
| 62 | else { |
|---|
| 63 | remote = remote_awars(application_id); |
|---|
| 64 | } |
|---|
| 65 | return error; |
|---|
| 66 | } |
|---|
| 67 | }; |
|---|
| 68 | |
|---|
| 69 | __ATTR__USERESULT static GB_ERROR check_for_remote_command(AW_root *aw_root, const db_interrupt_data& dib) { // @@@ split into several functions |
|---|
| 70 | arb_assert(!GB_have_error()); |
|---|
| 71 | |
|---|
| 72 | GB_ERROR error = NULp; |
|---|
| 73 | GBDATA *gb_main = dib.gb_main; |
|---|
| 74 | |
|---|
| 75 | GB_push_transaction(gb_main); // @@@ begin required/possible here? |
|---|
| 76 | |
|---|
| 77 | if (GB_is_server(gb_main)) { |
|---|
| 78 | char *client_action = GBT_readOrCreate_string(gb_main, MACRO_TRIGGER_TRACKED, ""); |
|---|
| 79 | if (client_action && client_action[0]) { |
|---|
| 80 | UserActionTracker *tracker = aw_root->get_tracker(); |
|---|
| 81 | MacroRecorder *macroRecorder = dynamic_cast<MacroRecorder*>(tracker); |
|---|
| 82 | |
|---|
| 83 | error = macroRecorder->handle_tracked_client_action(client_action); |
|---|
| 84 | GB_ERROR trig_error = GBT_write_string(gb_main, MACRO_TRIGGER_TRACKED, ""); // tell client that action has been recorded |
|---|
| 85 | if (!error) error = trig_error; |
|---|
| 86 | } |
|---|
| 87 | free(client_action); |
|---|
| 88 | |
|---|
| 89 | if (!error) { |
|---|
| 90 | GB_ERROR macro_error = GB_get_macro_error(gb_main); |
|---|
| 91 | if (macro_error) { |
|---|
| 92 | UserActionTracker *tracker = aw_root->get_tracker(); |
|---|
| 93 | MacroRecorder *macroRecorder = dynamic_cast<MacroRecorder*>(tracker); |
|---|
| 94 | |
|---|
| 95 | if (macroRecorder->is_tracking()) { // only handle recording error here |
|---|
| 96 | aw_message(GBS_global_string("%s\n" |
|---|
| 97 | "macro recording aborted", macro_error)); |
|---|
| 98 | error = macroRecorder->stop_recording(); |
|---|
| 99 | |
|---|
| 100 | GB_ERROR clr_error = GB_clear_macro_error(gb_main); |
|---|
| 101 | if (!error) error = clr_error; |
|---|
| 102 | } |
|---|
| 103 | } |
|---|
| 104 | } |
|---|
| 105 | } |
|---|
| 106 | |
|---|
| 107 | if (error) { |
|---|
| 108 | GB_pop_transaction(gb_main); |
|---|
| 109 | } |
|---|
| 110 | else { |
|---|
| 111 | const remote_awars& remote = dib.remote; |
|---|
| 112 | |
|---|
| 113 | long *granted = GBT_readOrCreate_int(gb_main, remote.granted(), 0); |
|---|
| 114 | pid_t pid = getpid(); |
|---|
| 115 | bool authorized = granted && *granted == pid; |
|---|
| 116 | |
|---|
| 117 | if (!authorized) { |
|---|
| 118 | if (!granted) error = GBS_global_string("Failed to access '%s'", remote.granted()); |
|---|
| 119 | else { |
|---|
| 120 | arb_assert(*granted != pid); |
|---|
| 121 | |
|---|
| 122 | // @@@ differ between *granted == 0 and *granted != 0? |
|---|
| 123 | |
|---|
| 124 | long *authReq = GBT_readOrCreate_int(gb_main, remote.authReq(), 0); |
|---|
| 125 | if (!authReq) error = GBS_global_string("Failed to access '%s'", remote.authReq()); |
|---|
| 126 | else if (*authReq) { |
|---|
| 127 | GBDATA *gb_authAck = GB_searchOrCreate_int(gb_main, remote.authAck(), 0); |
|---|
| 128 | if (!gb_authAck) error = GBS_global_string("Failed to access '%s'", remote.authAck()); |
|---|
| 129 | else { |
|---|
| 130 | pid_t authAck = GB_read_int(gb_authAck); |
|---|
| 131 | if (authAck == 0) { |
|---|
| 132 | // ack this process can execute remote commands |
|---|
| 133 | error = GB_write_int(gb_authAck, pid); |
|---|
| 134 | IF_DUMP_AUTH(fprintf(stderr, "acknowledging '%s' with pid %i\n", remote.authAck(), pid)); |
|---|
| 135 | } |
|---|
| 136 | else if (authAck == pid) { |
|---|
| 137 | // already acknowledged -- wait |
|---|
| 138 | } |
|---|
| 139 | else { // another process with same app-id acknowledged faster |
|---|
| 140 | IF_DUMP_AUTH(fprintf(stderr, "did not acknowledge '%s' with pid %i (pid %i was faster)\n", remote.authAck(), pid, authAck)); |
|---|
| 141 | const char *merr = GBS_global_string("Detected two clients with id '%s'", remote.appID()); |
|---|
| 142 | error = GB_set_macro_error(gb_main, merr); |
|---|
| 143 | } |
|---|
| 144 | } |
|---|
| 145 | } |
|---|
| 146 | } |
|---|
| 147 | error = GB_end_transaction(gb_main, error); |
|---|
| 148 | } |
|---|
| 149 | else { |
|---|
| 150 | char *action = GBT_readOrCreate_string(gb_main, remote.action(), ""); |
|---|
| 151 | char *value = GBT_readOrCreate_string(gb_main, remote.value(), ""); |
|---|
| 152 | char *tmp_awar = GBT_readOrCreate_string(gb_main, remote.awar(), ""); |
|---|
| 153 | |
|---|
| 154 | if (tmp_awar[0]) { |
|---|
| 155 | AW_awar *found_awar = aw_root->awar_no_error(tmp_awar); |
|---|
| 156 | if (!found_awar) { |
|---|
| 157 | error = GBS_global_string("Unknown variable '%s'", tmp_awar); |
|---|
| 158 | } |
|---|
| 159 | else { |
|---|
| 160 | if (strcmp(action, "AWAR_REMOTE_READ") == 0) { |
|---|
| 161 | char *read_value = aw_root->awar(tmp_awar)->read_as_string(); |
|---|
| 162 | GBT_write_string(gb_main, remote.value(), read_value); |
|---|
| 163 | IF_DUMP_ACTION(printf("remote command 'AWAR_REMOTE_READ' awar='%s' value='%s'\n", tmp_awar, read_value)); |
|---|
| 164 | free(read_value); |
|---|
| 165 | action[0] = 0; // clear action (AWAR_REMOTE_READ is just a pseudo-action) : |
|---|
| 166 | GBT_write_string(gb_main, remote.action(), ""); |
|---|
| 167 | } |
|---|
| 168 | else if (strcmp(action, "AWAR_REMOTE_TOUCH") == 0) { |
|---|
| 169 | GB_set_remote_action(gb_main, true); |
|---|
| 170 | aw_root->awar(tmp_awar)->touch(); |
|---|
| 171 | GB_set_remote_action(gb_main, false); |
|---|
| 172 | IF_DUMP_ACTION(printf("remote command 'AWAR_REMOTE_TOUCH' awar='%s'\n", tmp_awar)); |
|---|
| 173 | action[0] = 0; // clear action (AWAR_REMOTE_TOUCH is just a pseudo-action) : |
|---|
| 174 | GBT_write_string(gb_main, remote.action(), ""); |
|---|
| 175 | } |
|---|
| 176 | else { |
|---|
| 177 | IF_DUMP_ACTION(printf("remote command (write awar) awar='%s' value='%s'\n", tmp_awar, value)); |
|---|
| 178 | GB_set_remote_action(gb_main, true); |
|---|
| 179 | error = aw_root->awar(tmp_awar)->write_as_string(value); |
|---|
| 180 | GB_set_remote_action(gb_main, false); |
|---|
| 181 | } |
|---|
| 182 | } |
|---|
| 183 | GBT_write_string(gb_main, remote.result(), null2empty(error)); |
|---|
| 184 | GBT_write_string(gb_main, remote.awar(), ""); // tell perl-client call has completed (BIO::remote_awar and BIO:remote_read_awar) |
|---|
| 185 | |
|---|
| 186 | aw_message_if(error); |
|---|
| 187 | } |
|---|
| 188 | GB_pop_transaction(gb_main); // @@@ end required/possible here? |
|---|
| 189 | arb_assert(!GB_have_error()); // error exported by some callback? (unwanted) |
|---|
| 190 | |
|---|
| 191 | if (action[0]) { |
|---|
| 192 | AW_cb *act = aw_root->search_remote_command(action); |
|---|
| 193 | |
|---|
| 194 | if (act) { |
|---|
| 195 | IF_DUMP_ACTION(printf("remote command (%s) found, running callback\n", action)); |
|---|
| 196 | arb_assert(!GB_have_error()); |
|---|
| 197 | GB_set_remote_action(gb_main, true); |
|---|
| 198 | act->run_callbacks(); |
|---|
| 199 | GB_set_remote_action(gb_main, false); |
|---|
| 200 | arb_assert(!GB_have_error()); // error exported by callback (unwanted) |
|---|
| 201 | GBT_write_string(gb_main, remote.result(), ""); |
|---|
| 202 | } |
|---|
| 203 | else { |
|---|
| 204 | IF_DUMP_ACTION(printf("remote command (%s) is unknown\n", action)); |
|---|
| 205 | error = GBS_global_string("Unknown action '%s' in macro", action); |
|---|
| 206 | GBT_write_string(gb_main, remote.result(), error); |
|---|
| 207 | } |
|---|
| 208 | GBT_write_string(gb_main, remote.action(), ""); // tell perl-client call has completed (remote_action) |
|---|
| 209 | } |
|---|
| 210 | |
|---|
| 211 | free(tmp_awar); |
|---|
| 212 | free(value); |
|---|
| 213 | free(action); |
|---|
| 214 | } |
|---|
| 215 | } |
|---|
| 216 | |
|---|
| 217 | arb_assert(!GB_have_error()); |
|---|
| 218 | if (error) fprintf(stderr, "Error in check_for_remote_command: %s\n", error); |
|---|
| 219 | return error; |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | inline bool remote_command_handler(AW_root *awr, const db_interrupt_data& dib) { |
|---|
| 223 | // returns false in case of errors |
|---|
| 224 | arb_assert(!GB_have_error()); |
|---|
| 225 | |
|---|
| 226 | bool ok = true; |
|---|
| 227 | GB_ERROR error = check_for_remote_command(awr, dib); |
|---|
| 228 | if (error) { |
|---|
| 229 | aw_message(error); |
|---|
| 230 | ok = false; |
|---|
| 231 | } |
|---|
| 232 | return ok; |
|---|
| 233 | } |
|---|
| 234 | |
|---|
| 235 | static unsigned serve_db_interrupt(AW_root *awr, db_interrupt_data *dib) { // server |
|---|
| 236 | // always serves database connections (from clients). |
|---|
| 237 | // if awr!=null => also executes remote commands (normally from macro-clients) |
|---|
| 238 | // |
|---|
| 239 | // Possible improvement: maybe abort the following loop after some time? |
|---|
| 240 | // otherwise: if a client polls too fast, the GUI of the server may lock |
|---|
| 241 | |
|---|
| 242 | bool success = GBCMS_accept_calls(dib->gb_main, false); |
|---|
| 243 | while (success) { |
|---|
| 244 | success = (!awr || remote_command_handler(awr, *dib)) && GBCMS_accept_calls(dib->gb_main, true); |
|---|
| 245 | } |
|---|
| 246 | return ARB_SERVE_DB_TIMER; |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | static unsigned check_db_interrupt(AW_root *awr, db_interrupt_data *dib) { // client |
|---|
| 250 | remote_command_handler(awr, *dib); |
|---|
| 251 | return ARB_CHECK_DB_TIMER; |
|---|
| 252 | } |
|---|
| 253 | |
|---|
| 254 | // -------------------------------------------------------------------------------- |
|---|
| 255 | |
|---|
| 256 | static db_interrupt_data *idle_interrupt = NULp; |
|---|
| 257 | |
|---|
| 258 | GB_ERROR startup_dbserver(AW_root *aw_root, const char *application_id, GBDATA *gb_main) { |
|---|
| 259 | #if defined(DEBUG) |
|---|
| 260 | static bool initialized = false; |
|---|
| 261 | arb_assert(!initialized); // called twice - not able (yet) |
|---|
| 262 | initialized = true; |
|---|
| 263 | #endif |
|---|
| 264 | |
|---|
| 265 | arb_assert(got_macro_ability(aw_root)); |
|---|
| 266 | |
|---|
| 267 | GB_ERROR error = NULp; |
|---|
| 268 | if (GB_read_clients(gb_main) == 0) { // server |
|---|
| 269 | error = GBCMS_open(":", 0, gb_main); |
|---|
| 270 | if (error) { |
|---|
| 271 | error = GBS_global_string("THIS PROGRAM HAS PROBLEMS TO OPEN INTERCLIENT COMMUNICATION:\n" |
|---|
| 272 | "Reason: %s\n" |
|---|
| 273 | "(maybe there is already another server running)\n" |
|---|
| 274 | "You cannot use any EDITOR or other external SOFTWARE from here.\n" |
|---|
| 275 | "Advice: Close ARB again, open a console, type 'arb_clean' and restart arb.\n" |
|---|
| 276 | "Caution: Any unsaved data in an eventually running ARB will be lost.\n", |
|---|
| 277 | error); |
|---|
| 278 | } |
|---|
| 279 | else { |
|---|
| 280 | idle_interrupt = new db_interrupt_data(gb_main, application_id); |
|---|
| 281 | aw_root->add_timed_callback(ARB_SERVE_DB_TIMER, makeTimedCallback(serve_db_interrupt, idle_interrupt)); |
|---|
| 282 | } |
|---|
| 283 | } |
|---|
| 284 | else { // client |
|---|
| 285 | idle_interrupt = new db_interrupt_data(gb_main, application_id); |
|---|
| 286 | aw_root->add_timed_callback(ARB_CHECK_DB_TIMER, makeTimedCallback(check_db_interrupt, idle_interrupt)); |
|---|
| 287 | } |
|---|
| 288 | |
|---|
| 289 | if (!error) { |
|---|
| 290 | // handle remote commands once (to create DB-entries; w/o they are created after startup of first DB-client) |
|---|
| 291 | arb_assert(idle_interrupt); |
|---|
| 292 | if (idle_interrupt) remote_command_handler(aw_root, *idle_interrupt); |
|---|
| 293 | } |
|---|
| 294 | |
|---|
| 295 | return error; |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | GB_ERROR reconfigure_dbserver(const char *application_id, GBDATA *gb_main) { |
|---|
| 299 | // reconfigures a running dbserver (started with startup_dbserver) |
|---|
| 300 | arb_assert(idle_interrupt); // not started yet |
|---|
| 301 | return idle_interrupt->reconfigure(gb_main, application_id); |
|---|
| 302 | } |
|---|
| 303 | |
|---|
| 304 | GB_ERROR serve_db_while_GUI_is_blocked(GBDATA *gb_main) { |
|---|
| 305 | GB_ERROR error = NULp; |
|---|
| 306 | |
|---|
| 307 | if (!idle_interrupt) { |
|---|
| 308 | error = "called to early"; |
|---|
| 309 | } |
|---|
| 310 | else if (idle_interrupt->gb_main != gb_main) { |
|---|
| 311 | error = "detected multiple arb databases"; |
|---|
| 312 | } |
|---|
| 313 | else if (!GB_is_server(gb_main)) { |
|---|
| 314 | error = "i am not server"; |
|---|
| 315 | } |
|---|
| 316 | else { |
|---|
| 317 | // serve client requests w/o handling remote commands |
|---|
| 318 | // => macro does not continue |
|---|
| 319 | serve_db_interrupt(NULp, idle_interrupt); |
|---|
| 320 | } |
|---|
| 321 | |
|---|
| 322 | if (error) { |
|---|
| 323 | error = GBS_global_string("cannot serve DB (Reason: %s)", error); |
|---|
| 324 | } |
|---|
| 325 | |
|---|
| 326 | return error; |
|---|
| 327 | } |
|---|