source: trunk/ARBDB/adtools.cxx

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