source: tags/ms_r17q3/ARBDB/adtools.cxx

Last change on this file was 16469, checked in by westram, 7 years ago
  • reintegrates 'textedit' into 'trunk'
    • fixes #586
      • editor now always started asynchronously
      • uses inotify to track file changes
    • also use inotify to track directory updates (in order to update file selections when needed)
      (./) by [16515] ff.; merged by [16551]
  • adds: log:branches/textedit@16448:16468
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.4 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : adtools.cxx                                       //
4//   Purpose   : misc functions                                    //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "gb_data.h"
12#include "gb_main.h"
13
14#include <arb_sort.h>
15#include <arb_file.h>
16#include <arb_sleep.h>
17#include <arb_str.h>
18#include <arb_strarray.h>
19#include <ad_cb.h>
20
21#include <algorithm>
22#include "ad_remote.h"
23
24using namespace std;
25
26GBDATA *GBT_create(GBDATA *father, const char *key, long delete_level) {
27    GBDATA *gbd = GB_create_container(father, key);
28    if (gbd) {
29        GB_ERROR error = GB_write_security_delete(gbd, delete_level);
30        if (error) {
31            GB_export_error(error);
32            gbd = NULL; // assuming caller will abort the transaction
33        }
34    }
35    return gbd;
36}
37
38GBDATA *GBT_find_or_create(GBDATA *father, const char *key, long delete_level) {
39    GBDATA *gbd = GB_entry(father, key);
40    if (!gbd) gbd = GBT_create(father, key, delete_level);
41    return gbd;
42}
43
44
45/* the following functions were meant to use user defined values.
46 *
47 * Especially for 'ECOLI' there is already a possibility to
48 * specify a different reference in edit4, but there's no
49 * data model in the DB for it. Consider whether it makes sense,
50 * if secedit uses it as well.
51 *
52 * Note: Don't change the result type to 'const char *' even if the functions always
53 *       return the same atm. That may change.
54 */
55
56char *GBT_get_default_helix   (GBDATA *) { return ARB_strdup("HELIX"); }
57char *GBT_get_default_helix_nr(GBDATA *) { return ARB_strdup("HELIX_NR"); }
58char *GBT_get_default_ref     (GBDATA *) { return ARB_strdup("ECOLI"); }
59
60
61// ----------------
62//      scan DB
63
64#define GBT_SUM_LEN 4096                            // maximum length of path
65
66struct GB_DbScanner : virtual Noncopyable {
67    GB_HASH   *hash_table;
68    StrArray&  result; // not owned!
69    char      *buffer;
70
71    GB_DbScanner(StrArray& result_)
72        : result(result_)
73    {
74        hash_table = GBS_create_hash(1024, GB_MIND_CASE);
75        ARB_alloc(buffer, GBT_SUM_LEN);
76        buffer[0]  = 0;
77    }
78
79    ~GB_DbScanner() {
80        GBS_free_hash(hash_table);
81        free(buffer);
82    }
83};
84
85static void gbt_scan_db_rek(GBDATA *gbd, char *prefix, int deep, GB_DbScanner *scanner) {
86    GB_TYPES type = GB_read_type(gbd);
87    if (type == GB_DB) {
88        int len_of_prefix = strlen(prefix);
89        for (GBDATA *gb2 = GB_child(gbd); gb2; gb2 = GB_nextChild(gb2)) {  // find everything
90            if (deep) {
91                const char *key = GB_read_key_pntr(gb2);
92                if (key[0] != '@') { // skip internal containers
93                    sprintf(&prefix[len_of_prefix], "/%s", key);
94                    gbt_scan_db_rek(gb2, prefix, 1, scanner);
95                }
96            }
97            else {
98                prefix[len_of_prefix] = 0;
99                gbt_scan_db_rek(gb2, prefix, 1, scanner);
100            }
101        }
102        prefix[len_of_prefix] = 0;
103    }
104    else {
105        gb_assert(!prefix[0] || GB_check_hkey(prefix) == NULL);
106        LocallyModify<char> firstCharWithType(prefix[0], char(type)); // first char always is '/' -> use to store type in hash
107        GBS_incr_hash(scanner->hash_table, prefix);
108    }
109}
110
111struct scan_db_insert {
112    GB_DbScanner *scanner;
113    const char   *datapath;
114};
115
116static long gbs_scan_db_insert(const char *key, long val, void *cd_insert_data) {
117    scan_db_insert *insert    = (scan_db_insert *)cd_insert_data;
118    char           *to_insert = 0;
119
120    if (!insert->datapath) {
121        to_insert = ARB_strdup(key);
122    }
123    else {
124        bool do_insert = ARB_strBeginsWith(key+1, insert->datapath);
125        gb_assert(implicated(!do_insert, !ARB_strBeginsWith(insert->datapath, key+1))); // oops - previously inserted also in this case. inspect!
126
127        if (do_insert) { // datapath matches
128            to_insert    = ARB_strdup(key+strlen(insert->datapath)); // cut off prefix
129            to_insert[0] = key[0]; // copy type
130        }
131    }
132
133    if (to_insert) {
134        GB_DbScanner *scanner = insert->scanner;
135        scanner->result.put(to_insert);
136    }
137
138    return val;
139}
140
141static int gbs_scan_db_compare(const void *left, const void *right, void *) {
142    return strcmp((GB_CSTR)left+1, (GB_CSTR)right+1);
143}
144
145
146void GBT_scan_db(StrArray& fieldNames, GBDATA *gbd, const char *datapath) {
147    /*! scan CONTAINER for existing sub-keys
148     *
149     * recurses completely downwards the DB tree
150     *
151     * @param fieldNames gets filled with result strings
152     * - each string is the path to a node beyond gbd
153     * - every string exists only once
154     * - the first character of a string is the type of the entry
155     * - the strings are sorted alphabetically
156     * @param gbd node where search starts
157     * @param datapath if != NULL, only keys with prefix datapath are scanned and
158     * the prefix is removed from the resulting key_names.
159     *
160     * Note: this function is incredibly slow when called from clients
161     */
162
163    {
164        GB_DbScanner scanner(fieldNames);
165        gbt_scan_db_rek(gbd, scanner.buffer, 0, &scanner);
166        scan_db_insert insert = { &scanner, datapath, };
167        GBS_hash_do_loop(scanner.hash_table, gbs_scan_db_insert, &insert);
168    }
169    fieldNames.sort(gbs_scan_db_compare, 0);
170}
171
172// --------------------------
173//      message injection
174
175static void new_gbt_message_created_cb(GBDATA *gb_pending_messages) {
176    static int avoid_deadlock = 0;
177
178    if (!avoid_deadlock) {
179        GBDATA *gb_msg;
180
181        avoid_deadlock++;
182        GB_push_transaction(gb_pending_messages);
183
184        for (gb_msg = GB_entry(gb_pending_messages, "msg"); gb_msg;) {
185            {
186                const char *msg = GB_read_char_pntr(gb_msg);
187                GB_warning(msg);
188            }
189            {
190                GBDATA *gb_next_msg = GB_nextEntry(gb_msg);
191                GB_delete(gb_msg);
192                gb_msg              = gb_next_msg;
193            }
194        }
195
196        GB_pop_transaction(gb_pending_messages);
197        avoid_deadlock--;
198    }
199}
200
201inline GBDATA *find_or_create_error_container(GBDATA *gb_main) {
202    return GB_search(gb_main, ERROR_CONTAINER_PATH, GB_CREATE_CONTAINER);
203}
204
205void GBT_install_message_handler(GBDATA *gb_main) {
206    GBDATA *gb_pending_messages;
207
208    GB_push_transaction(gb_main);
209    gb_pending_messages = find_or_create_error_container(gb_main);
210    gb_assert(gb_pending_messages);
211    GB_add_callback(gb_pending_messages, GB_CB_SON_CREATED, makeDatabaseCallback(new_gbt_message_created_cb));
212    GB_pop_transaction(gb_main);
213
214#if defined(DEBUG) && 0
215    GBT_message(GB_get_root(gb_pending_messages), GBS_global_string("GBT_install_message_handler installed for gb_main=%p", gb_main));
216#endif // DEBUG
217}
218
219
220void GBT_message(GBDATA *gb_main, const char *msg) {
221    /*! When called in client(or server) this causes the DB server to show the message.
222     * Message is shown via GB_warning (which uses aw_message in GUIs)
223     *
224     * Note: The message is not shown before the transaction ends.
225     * If the transaction is aborted, the message is never shown!
226     *
227     * see also : GB_warning()
228     */
229
230    GB_ERROR error = GB_push_transaction(gb_main);
231
232    if (!error) {
233        GBDATA *gb_pending_messages = find_or_create_error_container(gb_main);
234        GBDATA *gb_msg              = gb_pending_messages ? GB_create(gb_pending_messages, "msg", GB_STRING) : 0;
235
236        if (!gb_msg) error = GB_await_error();
237        else {
238            gb_assert(msg);
239            error = GB_write_string(gb_msg, msg);
240        }
241    }
242    error = GB_end_transaction(gb_main, error);
243
244    if (error) {
245        fprintf(stderr, "GBT_message: Failed to write message '%s'\n(Reason: %s)\n", msg, error);
246    }
247}
248
249char *GBT_read_string(GBDATA *gb_container, const char *fieldpath) {
250    /*! Read value from database field (of type GB_STRING)
251     *
252     * @param gb_container where to start search for field
253     * @param fieldpath relative path from gb_container
254     *
255     * @return
256     * NULL in case of error (use GB_await_error()) or when field does not exist.
257     * otherwise returns a heap copy.
258     *
259     * other functions return a pointer to a temporary variable (invalidated by next call)
260     */
261
262    GBDATA *gbd;
263    char   *result = NULL;
264
265    GB_push_transaction(gb_container);
266    gbd = GB_search(gb_container, fieldpath, GB_FIND);
267    if (gbd) result = GB_read_string(gbd);
268    GB_pop_transaction(gb_container);
269    return result;
270}
271
272char *GBT_read_as_string(GBDATA *gb_container, const char *fieldpath) {
273    /*! like GBT_read_string()
274     *
275     * but
276     * - field may be of any type (result gets converted to reasonable char*)
277     */
278
279    GBDATA *gbd;
280    char   *result = NULL;
281
282    GB_push_transaction(gb_container);
283    gbd = GB_search(gb_container, fieldpath, GB_FIND);
284    if (gbd) result = GB_read_as_string(gbd);
285    GB_pop_transaction(gb_container);
286    return result;
287}
288
289const char *GBT_read_char_pntr(GBDATA *gb_container, const char *fieldpath) {
290    /*! like GBT_read_string()
291     *
292     * but
293     * - result is not a heap-copy and may be invalidated by further DB access
294     *   (especially by overwriting that fields)
295     *
296     * Note: Under no circumstances you may modify the result!
297     */
298
299    GBDATA     *gbd;
300    const char *result = NULL;
301
302    GB_push_transaction(gb_container);
303    gbd = GB_search(gb_container, fieldpath, GB_FIND);
304    if (gbd) result = GB_read_char_pntr(gbd);
305    GB_pop_transaction(gb_container);
306    return result;
307}
308
309NOT4PERL long *GBT_read_int(GBDATA *gb_container, const char *fieldpath) {
310    /*! similar to GBT_read_string()
311     *
312     * but
313     * - for fields of type GB_INT
314     * - result gets invalidated by next call
315     */
316
317    GBDATA *gbd;
318    long   *result = NULL;
319
320    GB_push_transaction(gb_container);
321    gbd = GB_search(gb_container, fieldpath, GB_FIND);
322    if (gbd) {
323        static long result_var;
324        result_var = GB_read_int(gbd);
325        result     = &result_var;
326    }
327    GB_pop_transaction(gb_container);
328    return result;
329}
330
331NOT4PERL float *GBT_read_float(GBDATA *gb_container, const char *fieldpath) {
332    /*! similar to GBT_read_string()
333     *
334     * but
335     * - for fields of type GB_FLOAT
336     * - result gets invalidated by next call
337     */
338
339    GBDATA *gbd;
340    float  *result = NULL;
341
342    GB_push_transaction(gb_container);
343    gbd = GB_search(gb_container, fieldpath, GB_FIND);
344    if (gbd) {
345        static float result_var;
346        result_var = GB_read_float(gbd);
347        result     = &result_var;
348    }
349    GB_pop_transaction(gb_container);
350    return result;
351}
352
353char *GBT_readOrCreate_string(GBDATA *gb_container, const char *fieldpath, const char *default_value) {
354    /*! like GBT_read_string(),
355     *
356     * but if field does not exist, it will be created and initialized with 'default_value'
357     */
358    GBDATA *gb_string;
359    char   *result = NULL;
360
361    GB_push_transaction(gb_container);
362    gb_string             = GB_searchOrCreate_string(gb_container, fieldpath, default_value);
363    if (gb_string) result = GB_read_string(gb_string);
364    GB_pop_transaction(gb_container);
365    return result;
366}
367
368const char *GBT_readOrCreate_char_pntr(GBDATA *gb_container, const char *fieldpath, const char *default_value) {
369    /*! like GBT_read_char_pntr(),
370     *
371     * but if field does not exist, it will be created and initialized with 'default_value'
372     */
373
374    GBDATA     *gb_string;
375    const char *result = NULL;
376
377    GB_push_transaction(gb_container);
378    gb_string             = GB_searchOrCreate_string(gb_container, fieldpath, default_value);
379    if (gb_string) result = GB_read_char_pntr(gb_string);
380    GB_pop_transaction(gb_container);
381    return result;
382}
383
384NOT4PERL long *GBT_readOrCreate_int(GBDATA *gb_container, const char *fieldpath, long default_value) {
385    /*! like GBT_read_int(),
386     *
387     * but if field does not exist, it will be created and initialized with 'default_value'
388     */
389
390    GBDATA *gb_int;
391    long   *result = NULL;
392
393    GB_push_transaction(gb_container);
394    gb_int = GB_searchOrCreate_int(gb_container, fieldpath, default_value);
395    if (gb_int) {
396        static long result_var;
397        result_var = GB_read_int(gb_int);
398        result     = &result_var;
399    }
400    GB_pop_transaction(gb_container);
401    return result;
402}
403
404NOT4PERL float *GBT_readOrCreate_float(GBDATA *gb_container, const char *fieldpath, float default_value) {
405    /*! like GBT_read_float(),
406     *
407     * but if field does not exist, it will be created and initialized with 'default_value'
408     */
409
410    gb_assert(default_value == default_value); // !nan
411
412    GBDATA *gb_float;
413    float  *result = NULL;
414
415    GB_push_transaction(gb_container);
416    gb_float = GB_searchOrCreate_float(gb_container, fieldpath, default_value);
417    if (gb_float) {
418        static float result_var;
419        result_var = GB_read_float(gb_float);
420        result     = &result_var;
421    }
422    else {
423        gb_assert(0);
424    }
425    GB_pop_transaction(gb_container);
426    return result;
427}
428
429// -------------------------------------------------------------------
430//      overwrite existing or create new database field
431//      (field must not exist twice or more - it has to be unique!!)
432
433GB_ERROR GBT_write_string(GBDATA *gb_container, const char *fieldpath, const char *content) {
434    /*! Write content to database field of type GB_STRING
435     *
436     * if field exists, it will be overwritten.
437     * if field does not exist, it will be created.
438     *
439     * The field should be unique, i.e. it should not exist twice or more in it's parent container
440     *
441     * @return GB_ERROR on failure
442     */
443    GB_ERROR  error = GB_push_transaction(gb_container); // @@@ result unused
444    GBDATA   *gbd   = GB_search(gb_container, fieldpath, GB_STRING);
445    if (!gbd) error = GB_await_error();
446    else {
447        error = GB_write_string(gbd, content);
448        gb_assert(GB_nextEntry(gbd) == 0); // only one entry should exist (sure you want to use this function?)
449    }
450    return GB_end_transaction(gb_container, error);
451}
452
453GB_ERROR GBT_write_int(GBDATA *gb_container, const char *fieldpath, long content) {
454    /*! like GBT_write_string(),
455     *
456     * but for fields of type GB_INT
457     */
458    GB_ERROR  error = GB_push_transaction(gb_container); // @@@ result unused
459    GBDATA   *gbd   = GB_search(gb_container, fieldpath, GB_INT);
460    if (!gbd) error = GB_await_error();
461    else {
462        error = GB_write_int(gbd, content);
463        gb_assert(GB_nextEntry(gbd) == 0); // only one entry should exist (sure you want to use this function?)
464    }
465    return GB_end_transaction(gb_container, error);
466}
467
468GB_ERROR GBT_write_byte(GBDATA *gb_container, const char *fieldpath, unsigned char content) {
469    /*! like GBT_write_string(),
470     *
471     * but for fields of type GB_BYTE
472     */
473    GB_ERROR  error = GB_push_transaction(gb_container); // @@@ result unused
474    GBDATA   *gbd   = GB_search(gb_container, fieldpath, GB_BYTE);
475    if (!gbd) error = GB_await_error();
476    else {
477        error = GB_write_byte(gbd, content);
478        gb_assert(GB_nextEntry(gbd) == 0); // only one entry should exist (sure you want to use this function?)
479    }
480    return GB_end_transaction(gb_container, error);
481}
482
483
484GB_ERROR GBT_write_float(GBDATA *gb_container, const char *fieldpath, float content) {
485    /*! like GBT_write_string(),
486     *
487     * but for fields of type GB_FLOAT
488     */
489
490    gb_assert(content == content); // !nan
491
492    GB_ERROR  error = GB_push_transaction(gb_container); // @@@ result unused
493    GBDATA   *gbd   = GB_search(gb_container, fieldpath, GB_FLOAT);
494    if (!gbd) error = GB_await_error();
495    else {
496        error = GB_write_float(gbd, content);
497        gb_assert(GB_nextEntry(gbd) == 0); // only one entry should exist (sure you want to use this function?)
498    }
499    return GB_end_transaction(gb_container, error);
500}
501
502
503// --------------------
504//      save & load
505
506GBDATA *GBT_open(const char *path, const char *opent) {
507    /*! Open a database (as GB_open does)
508     *  Additionally:
509     *  - disable saving in the PT_SERVER directory,
510     *  - create an index for species and extended names (server only!),
511     *
512     * @param path filename of the DB
513     * @param opent see GB_login()
514     * @return database handle (or NULL in which case an error is exported)
515     * @see GB_open()
516     */
517
518    GBDATA *gbd = GB_open(path, opent);
519    if (gbd) {
520        GB_disable_path(gbd, GB_path_in_ARBLIB("pts/*"));
521        GB_ERROR error = NULL;
522        {
523            GB_transaction ta(gbd);
524
525            if (!strchr(path, ':')) {
526                GBDATA *species_data = GB_search(gbd, "species_data", GB_FIND); // do NOT create species_data here!
527                if (species_data) {
528                    long hash_size = max(GB_number_of_subentries(species_data), GBT_SPECIES_INDEX_SIZE);
529                    error          = GB_create_index(species_data, "name", GB_IGNORE_CASE, hash_size);
530
531                    if (!error) {
532                        GBDATA *extended_data = GBT_get_SAI_data(gbd);
533                        hash_size             = max(GB_number_of_subentries(extended_data), GBT_SAI_INDEX_SIZE);
534                        error                 = GB_create_index(extended_data, "name", GB_IGNORE_CASE, hash_size);
535                    }
536                }
537            }
538        }
539        if (error) {
540            GB_close(gbd);
541            gbd = NULL;
542            GB_export_error(error);
543        }
544    }
545    gb_assert(contradicted(gbd, GB_have_error()));
546    return gbd;
547}
548
549/* --------------------------------------------------------------------------------
550 * Remote commands
551 *
552 * Note: These commands may seem to be unused from inside ARB.
553 * They get used, but only indirectly via the macro-function.
554 *
555 * Search for
556 * - BIO::remote_action         (use of GBT_remote_action)
557 * - BIO::remote_awar           (use of GBT_remote_awar)
558 * - BIO::remote_read_awar      (use of GBT_remote_read_awar)
559 */
560
561static GBDATA *wait_for_dbentry(GBDATA *gb_main, const char *entry, const ARB_timeout *timeout, bool verbose) {
562    // if 'timeout' != NULL -> abort and return NULL if timeout has passed (otherwise wait forever)
563
564    if (verbose) GB_warningf("[waiting for DBENTRY '%s']", entry);
565    GBDATA *gbd;
566    {
567        ARB_timestamp  start;
568        MacroTalkSleep increasing;
569
570        while (1) {
571            GB_begin_transaction(gb_main);
572            gbd = GB_search(gb_main, entry, GB_FIND);
573            GB_commit_transaction(gb_main);
574            if (gbd) break;
575            if (timeout && timeout->passed()) break;
576            increasing.sleep();
577        }
578    }
579    if (gbd && verbose) GB_warningf("[found DBENTRY '%s']", entry);
580    return gbd;
581}
582
583static GB_ERROR gbt_wait_for_remote_action(GBDATA *gb_main, GBDATA *gb_action, const char *awar_read) {
584    // waits until remote action has finished
585
586    MacroTalkSleep increasing;
587    GB_ERROR error = 0;
588    while (!error) {
589        increasing.sleep();
590        error = GB_begin_transaction(gb_main);
591        if (!error) {
592            char *ac = GB_read_string(gb_action);
593            if (ac[0] == 0) { // action has been cleared from remote side
594                GBDATA *gb_result = GB_search(gb_main, awar_read, GB_STRING);
595                error             = GB_read_char_pntr(gb_result); // check for errors
596            }
597            free(ac);
598        }
599        error = GB_end_transaction(gb_main, error);
600    }
601
602    if (error && !error[0]) return NULL; // empty error means ok
603    return error; // may be error or result
604}
605
606static void mark_as_macro_executor(GBDATA *gb_main, bool mark) {
607    // call this with 'mark' = true to mark yourself as "client running a macro"
608    // -> GB_close will automatically announce termination of this client to main DB (MACRO_TRIGGER_TERMINATED)
609    // do NOT call with 'mark' = false
610
611    static bool client_is_macro_executor = false;
612    if (mark) {
613        if (!client_is_macro_executor) {
614            GB_atclose(gb_main, (void (*)(GBDATA*, void*))mark_as_macro_executor, 0);
615            client_is_macro_executor = true;
616        }
617    }
618    else {
619        // called via GB_atclose callback (i.e. at end of currently executed macro file)
620        GB_ERROR error = NULL;
621        gb_assert(client_is_macro_executor);
622        if (client_is_macro_executor) {
623            GB_transaction ta(gb_main);
624
625            GBDATA *gb_terminated = GB_search(gb_main, MACRO_TRIGGER_TERMINATED, GB_FIND);
626            gb_assert(gb_terminated); // should have been created by macro caller
627            if (gb_terminated) {
628                error = GB_write_int(gb_terminated, GB_read_int(gb_terminated)+1); // notify macro caller
629            }
630
631            error = ta.close(error);
632        }
633        if (error) GBT_message(gb_main, error);
634    }
635}
636
637inline GB_ERROR set_intEntry_to(GBDATA *gb_main, const char *path, int value) {
638    GBDATA *gbd = GB_searchOrCreate_int(gb_main, path, value);
639    return gbd ? GB_write_int(gbd, value) : GB_await_error();
640}
641
642#if defined(DEBUG)
643// # define DUMP_AUTH_HANDSHAKE // see also ../SL/MACROS/dbserver.cxx@DUMP_AUTHORIZATION
644#endif
645
646#if defined(DUMP_AUTH_HANDSHAKE)
647# define IF_DUMP_HANDSHAKE(cmd) cmd
648#else
649# define IF_DUMP_HANDSHAKE(cmd)
650#endif
651
652GB_ERROR GB_set_macro_error(GBDATA *gb_main, const char *curr_error) {
653    GB_ERROR        error          = NULL;
654    GB_transaction  ta(gb_main);
655    GBDATA         *gb_macro_error = GB_searchOrCreate_string(gb_main, MACRO_TRIGGER_ERROR, curr_error);
656    if (gb_macro_error) {
657        const char *prev_error = GB_read_char_pntr(gb_macro_error);
658        if (prev_error && prev_error[0]) { // already have an error
659            if (strstr(prev_error, curr_error) == 0) { // do not add message twice
660                error = GB_write_string(gb_macro_error, GBS_global_string("%s\n%s", prev_error, curr_error));
661            }
662        }
663        else {
664            error = GB_write_string(gb_macro_error, curr_error);
665        }
666    }
667    return error;
668}
669GB_ERROR GB_get_macro_error(GBDATA *gb_main) {
670    GB_ERROR error = NULL;
671
672    GB_transaction  ta(gb_main);
673    GBDATA         *gb_macro_error = GB_search(gb_main, MACRO_TRIGGER_ERROR, GB_FIND);
674    if (gb_macro_error) {
675        const char *macro_error       = GB_read_char_pntr(gb_macro_error);
676        if (!macro_error) macro_error = GBS_global_string("failed to retrieve error message (Reason: %s)", GB_await_error());
677        if (macro_error[0]) error     = GBS_global_string("macro-error: %s", macro_error);
678    }
679    return error;
680}
681GB_ERROR GB_clear_macro_error(GBDATA *gb_main) {
682    GB_transaction  ta(gb_main);
683    GB_ERROR        error          = NULL;
684    GBDATA         *gb_macro_error = GB_search(gb_main, MACRO_TRIGGER_ERROR, GB_FIND);
685    if (gb_macro_error) error      = GB_write_string(gb_macro_error, "");
686    return error;
687}
688
689static GB_ERROR start_remote_command_for_application(GBDATA *gb_main, const remote_awars& remote, const ARB_timeout *timeout, bool verbose) {
690    // Called before any remote command will be written to DB.
691    // Application specific initialization is done here.
692    //
693    // if 'timeout' != NULL -> abort with error if timeout has passed (otherwise wait forever)
694
695    ARB_timestamp start;
696    bool          wait_for_app = false;
697
698    GB_ERROR error    = GB_begin_transaction(gb_main);
699    if (!error) error = GB_get_macro_error(gb_main);
700    if (!error) {
701        GBDATA *gb_granted     = GB_searchOrCreate_int(gb_main, remote.granted(), 0);
702        if (!gb_granted) error = GB_await_error();
703        else {
704            if (GB_read_int(gb_granted)) {
705                // ok - authorization already granted
706            }
707            else {
708                error        = set_intEntry_to(gb_main, remote.authReq(), 1); // ask client to ack execution
709                wait_for_app = !error;
710
711                if (!error) {
712                    IF_DUMP_HANDSHAKE(fprintf(stderr, "AUTH_HANDSHAKE [set %s to 1]\n", remote.authReq()));
713                }
714            }
715        }
716    }
717    error = GB_end_transaction(gb_main, error);
718
719    MacroTalkSleep increasing;
720    while (wait_for_app) {
721        gb_assert(!error);
722
723        IF_DUMP_HANDSHAKE(fprintf(stderr, "AUTH_HANDSHAKE [waiting for %s]\n", remote.authAck()));
724
725        GBDATA *gb_authAck = wait_for_dbentry(gb_main, remote.authAck(), timeout, verbose);
726        if (gb_authAck) {
727            error = GB_begin_transaction(gb_main);
728            if (!error) {
729                long ack_pid = GB_read_int(gb_authAck);
730                if (ack_pid) {
731                    IF_DUMP_HANDSHAKE(fprintf(stderr, "AUTH_HANDSHAKE [got authAck %li]\n", ack_pid));
732                   
733                    GBDATA *gb_granted = GB_searchOrCreate_int(gb_main, remote.granted(), ack_pid);
734                    long    old_pid    = GB_read_int(gb_granted);
735
736                    if (old_pid != ack_pid) { // we have two applications with same id that acknowledged the execution request
737                        if (old_pid == 0) {
738                            error             = GB_write_int(gb_granted, ack_pid); // grant rights to execute remote-command to ack_pid
739                            if (!error) error = GB_write_int(gb_authAck, 0);       // allow a second application to acknowledge the request
740                            if (!error) {
741                                wait_for_app = false;
742                                IF_DUMP_HANDSHAKE(fprintf(stderr, "AUTH_HANDSHAKE [granted permission to execute macros to pid %li]\n", ack_pid));
743                            }
744                        }
745                    }
746                    else {
747                        error = GB_write_int(gb_authAck, 0); // allow a second application to acknowledge the request
748                    }
749                }
750                else {
751                    // old entry with value 0 -> wait until client acknowledges authReq
752                    IF_DUMP_HANDSHAKE(fprintf(stderr, "AUTH_HANDSHAKE [no %s yet]\n", remote.authAck()));
753                }
754            }
755            error = GB_end_transaction(gb_main, error);
756        }
757        else {
758            // happens after timeout (handled by next if-clause)
759        }
760
761        if (timeout && timeout->passed()) {
762            wait_for_app = false;
763            error        = "remote application did not answer (within timeout)";
764        }
765
766        if (wait_for_app) {
767            increasing.sleep();
768        }
769    }
770
771    return error;
772}
773
774NOT4PERL GB_ERROR GBT_remote_action_with_timeout(GBDATA *gb_main, const char *application, const char *action_name, const class ARB_timeout *timeout, bool verbose) {
775    // if timeout_ms > 0 -> abort with error if application does not answer (e.g. is not running)
776    // Note: opposed to GBT_remote_action, this function may NOT be called directly by perl-macros
777
778    remote_awars remote(application);
779    GB_ERROR     error = start_remote_command_for_application(gb_main, remote, timeout, verbose);
780
781    if (!error) {
782        GBDATA *gb_action = wait_for_dbentry(gb_main, remote.action(), NULL, false);
783        error             = GB_begin_transaction(gb_main);
784        if (!error) error = GB_write_string(gb_action, action_name); // write command
785        error             = GB_end_transaction(gb_main, error);
786        if (!error) error = gbt_wait_for_remote_action(gb_main, gb_action, remote.result());
787    }
788    return error;
789}
790
791GB_ERROR GBT_remote_action(GBDATA *gb_main, const char *application, const char *action_name) {
792    // needs to be public (used exclusively(!) by perl-macros)
793    mark_as_macro_executor(gb_main, true);
794    return GBT_remote_action_with_timeout(gb_main, application, action_name, 0, true);
795}
796
797GB_ERROR GBT_remote_awar(GBDATA *gb_main, const char *application, const char *awar_name, const char *value) {
798    // needs to be public (used exclusively(!) by perl-macros)
799
800    mark_as_macro_executor(gb_main, true);
801
802    remote_awars remote(application);
803    GB_ERROR     error = start_remote_command_for_application(gb_main, remote, 0, true);
804
805    if (!error) {
806        GBDATA *gb_awar   = wait_for_dbentry(gb_main, remote.awar(), 0, false);
807        error             = GB_begin_transaction(gb_main);
808        if (!error) error = GB_write_string(gb_awar, awar_name);
809        if (!error) error = GBT_write_string(gb_main, remote.value(), value);
810        error             = GB_end_transaction(gb_main, error);
811        if (!error) error = gbt_wait_for_remote_action(gb_main, gb_awar, remote.result());
812    }
813    return error;
814}
815
816GB_ERROR GBT_remote_read_awar(GBDATA *gb_main, const char *application, const char *awar_name) {
817    // needs to be public (used exclusively(!) by perl-macros)
818
819    mark_as_macro_executor(gb_main, true);
820
821    remote_awars remote(application);
822    GB_ERROR     error = start_remote_command_for_application(gb_main, remote, 0, true);
823
824    if (!error) {
825        GBDATA *gb_awar   = wait_for_dbentry(gb_main, remote.awar(), 0, false);
826        error             = GB_begin_transaction(gb_main);
827        if (!error) error = GB_write_string(gb_awar, awar_name);
828        if (!error) error = GBT_write_string(gb_main, remote.action(), "AWAR_REMOTE_READ");
829        error             = GB_end_transaction(gb_main, error);
830        if (!error) error = gbt_wait_for_remote_action(gb_main, gb_awar, remote.value());
831    }
832    return error;
833}
834
835inline char *find_macro_in(const char *dir, const char *macroname) {
836    char *full = GBS_global_string_copy("%s/%s", dir, macroname);
837    if (!GB_is_readablefile(full)) {
838        freeset(full, GBS_global_string_copy("%s.amc", full));
839        if (!GB_is_readablefile(full)) freenull(full);
840    }
841    return full;
842}
843
844static char *fullMacroname(const char *macro_name) {
845    /*! detect full path of 'macro_name'
846     * @param macro_name full path or path relative to ARBMACRO or ARBMACROHOME
847     * @return full path or NULL (in which case an error is exported)
848     */
849
850    gb_assert(!GB_have_error());
851
852    if (GB_is_readablefile(macro_name)) return ARB_strdup(macro_name);
853
854    char *in_ARBMACROHOME = find_macro_in(GB_getenvARBMACROHOME(), macro_name);
855    char *in_ARBMACRO     = find_macro_in(GB_getenvARBMACRO(),     macro_name);
856    char *result          = NULL;
857
858    if (in_ARBMACROHOME) {
859        if (in_ARBMACRO) {
860            GB_export_errorf("ambiguous macro name '%s'\n"
861                             "('%s' and\n"
862                             " '%s' exist both.\n"
863                             " You have to rename or delete one of them!)",
864                             macro_name, in_ARBMACROHOME, in_ARBMACRO);
865        }
866        else reassign(result, in_ARBMACROHOME);
867    }
868    else {
869        if (in_ARBMACRO) reassign(result, in_ARBMACRO);
870        else GB_export_errorf("Failed to detect macro '%s'", macro_name);
871    }
872
873    free(in_ARBMACRO);
874    free(in_ARBMACROHOME);
875
876    gb_assert(contradicted(result, GB_have_error()));
877
878    return result;
879}
880
881inline const char *relative_inside(const char *dir, const char *fullpath) {
882    if (ARB_strBeginsWith(fullpath, dir)) {
883        const char *result = fullpath+strlen(dir);
884        if (result[0] == '/') return result+1;
885    }
886    return NULL;
887}
888
889const char *GBT_relativeMacroname(const char *macro_name) {
890    /*! make macro_name relative if it is located in or below ARBMACROHOME or ARBMACRO.
891     * Inverse function of fullMacroname()
892     * @return pointer into macro_name (relative part) or macro_name itself (if macro is located somewhere else)
893     */
894
895    const char *result  = relative_inside(GB_getenvARBMACROHOME(), macro_name);
896    if (!result) result = relative_inside(GB_getenvARBMACRO(), macro_name);
897    if (!result) result = macro_name;
898    return result;
899}
900
901GB_ERROR GBT_macro_execute(const char *macro_name, bool loop_marked, bool run_async) {
902    /*! simply execute a macro
903     *
904     * In doubt: do not use this function, instead use ../SL/MACROS/macros.hxx@execute_macro
905     *
906     * @param macro_name contains the macro filename (either with full path or relative to directories pointed to by ARBMACRO or ARBMACROHOME)
907     * @param loop_marked if true -> call macro once for each marked species
908     * @param run_async if true -> run perl asynchronously (will not detect perl failure in that case!)
909     */
910
911    GB_ERROR  error     = NULL;
912    char     *fullMacro = fullMacroname(macro_name);
913    if (!fullMacro) {
914        error = GB_await_error();
915    }
916    else {
917        char *perl_args = NULL;
918        freeset(fullMacro, GBK_singlequote(fullMacro));
919        if (loop_marked) {
920            const char *with_all_marked = GB_path_in_ARBHOME("PERL_SCRIPTS/MACROS/with_all_marked.pl");
921            perl_args = GBS_global_string_copy("'%s' %s", with_all_marked, fullMacro);
922        }
923        else {
924            perl_args = GBS_global_string_copy("%s", fullMacro);
925        }
926
927        char *cmd = GBS_global_string_copy("perl %s %s", perl_args, run_async ? "&" : "");
928
929        error = GBK_system(cmd);
930
931        free(cmd);
932        free(perl_args);
933        free(fullMacro);
934    }
935    return error;
936}
937
938// --------------------------------------------------------------------------------
939
940#ifdef UNIT_TESTS
941#include <test_unit.h>
942
943#define TEST_EXPECT_SCANNED_EQUALS(path,expected) do {  \
944        GB_transaction ta(gb_main);                     \
945        StrArray fields;                                \
946        GBT_scan_db(fields, gb_main, path);             \
947        char  *joined = GBT_join_strings(fields, ',');  \
948        TEST_EXPECT_EQUAL(joined, expected);            \
949        free(joined);                                   \
950    } while (0)
951
952void TEST_scan_db() {
953    GB_shell  shell;
954    GBDATA   *gb_main = GB_open("TEST_loadsave.arb", "r");
955
956    TEST_EXPECT_SCANNED_EQUALS(NULL,
957                               "\x03""alignment/aligned,\x03""alignment/alignment_len,\falignment/alignment_name,\falignment/alignment_rem,\falignment/alignment_type,\x03""alignment/alignment_write_security,\x03""alignment/auto_format,"
958                               "\x02""byte,"
959#if defined(DARWIN)
960                               // @@@ RESULT_MODIFIED_OSX: why does that happen?
961                               "\bbytes,\fbytes,\x03""bytes,"
962#else // !DARWIN
963                               "\x03""bytes,\bbytes,\fbytes,"
964#endif
965                               "\fextended/acc,\fextended/ali_16s/_TYPE,\x06""extended/ali_16s/bits,\fextended/ali_16s/data,\fextended/aligned,\x03""extended/errors,\fextended/full_name,\fextended/name,"
966                               "\nfloats,\tints,\tints_empty,"
967                               "\x03key_data/key/key_hidden,\fkey_data/key/key_name,\x03key_data/key/key_type,"
968                               "\fspecies/AL,\x03species/ARB_color,\fspecies/acc,\fspecies/ali_16s/data,\x06species/bits_test,\x04species/float_test,\fspecies/full_name,\fspecies/name,\fspecies/seqcheck,\fspecies/tax,"
969                               "\fstr,\fstr_percent,\fuse");
970
971    TEST_EXPECT_SCANNED_EQUALS("species", "\f/AL,\x03/ARB_color,\f/acc,\f/ali_16s/data,\x06/bits_test,\x04/float_test,\f/full_name,\f/name,\f/seqcheck,\f/tax");
972
973    TEST_REJECT(GB_have_error());
974   
975    GB_close(gb_main);
976}
977
978static arb_test::match_expectation macroFoundAs(const char *shortName, const char *expectedFullName_tmp, const char *expectedRelName, const char *partOfError) {
979    char *expectedFullName = nulldup(expectedFullName_tmp);
980
981    using namespace   arb_test;
982    expectation_group expected;
983
984    {
985        char *found = fullMacroname(shortName);
986        expected.add(that(found).is_equal_to(expectedFullName));
987        if (found) {
988            expected.add(that(GBT_relativeMacroname(found)).is_equal_to(expectedRelName));
989        }
990        free(found);
991    }
992
993    GB_ERROR error = GB_incur_error();
994    if (partOfError) {
995        if (error) {
996            expected.add(that(error).does_contain(partOfError));
997        }
998        else {
999            expected.add(that(error).does_differ_from_NULL());
1000        }
1001    }
1002    else {
1003        expected.add(that(error).is_equal_to_NULL());
1004    }
1005
1006    free(expectedFullName);
1007    return all().ofgroup(expected);
1008}
1009
1010#define TEST_FULLMACRO_EQUALS(shortName,fullName,relName) TEST_EXPECTATION(macroFoundAs(shortName, fullName, relName, NULL))
1011#define TEST_FULLMACRO_FAILS(shortName,expectedErrorPart) TEST_EXPECTATION(macroFoundAs(shortName, NULL, NULL, expectedErrorPart))
1012
1013void TEST_find_macros() {
1014    gb_getenv_hook old = GB_install_getenv_hook(arb_test::fakeenv);
1015
1016#define TEST         "test"
1017#define RESERVED     "reserved4ut"
1018#define TEST_AMC     TEST ".amc"
1019#define RESERVED_AMC RESERVED ".amc"
1020
1021    // unlink test.amc in ARBMACROHOME (from previous run)
1022    // ../UNIT_TESTER/run/homefake/.arb_prop/macros
1023    char *test_amc = ARB_strdup(GB_concat_path(GB_getenvARBMACROHOME(), TEST_AMC));
1024    char *res_amc  = ARB_strdup(GB_concat_path(GB_getenvARBMACROHOME(), RESERVED_AMC));
1025
1026    TEST_EXPECT_DIFFERENT(GB_unlink(test_amc), -1);
1027    TEST_EXPECT_DIFFERENT(GB_unlink(res_amc), -1);
1028
1029    // check if it finds macros in ARBMACRO = ../lib/macros
1030    TEST_FULLMACRO_EQUALS(TEST, GB_path_in_ARBLIB("macros/" TEST_AMC), TEST_AMC);
1031
1032    // searching reserved4ut.amc should fail now (macro should not exists)
1033    TEST_FULLMACRO_FAILS(RESERVED, "Failed to detect");
1034
1035    // --------------------------------
1036    // create 2 macros in ARBMACROHOME
1037    TEST_EXPECT_NO_ERROR(GBK_system(GBS_global_string("touch '%s'; touch '%s'", test_amc, res_amc)));
1038
1039    // searching test.amc should fail now (macro exists in ARBMACROHOME and ARBMACRO)
1040    TEST_FULLMACRO_FAILS(TEST, "ambiguous macro name");
1041
1042    // check if it finds macros in ARBMACROHOME = ../UNIT_TESTER/run/homefake/.arb_prop/macros
1043    TEST_FULLMACRO_EQUALS(RESERVED, res_amc, RESERVED_AMC);
1044
1045    free(res_amc);
1046    free(test_amc);
1047
1048    TEST_EXPECT_EQUAL((void*)arb_test::fakeenv, (void*)GB_install_getenv_hook(old));
1049}
1050TEST_PUBLISH(TEST_find_macros);
1051
1052#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.