source: branches/species/NTREE/NT_main.cxx

Last change on this file was 19691, checked in by westram, 2 weeks ago
  • reintegrates 'macros' into 'trunk'
    • fixes macro IDs for mergetool (completing #870).
      • most common problem:
        • several modules were reused (twice) for items of same type, but in different databases.
        • auto-generated IDs were identical.
  • adds: log:branches/macros@19667:19690
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.7 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : NT_main.cxx                                       //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "NT_local.h"
12
13#include <mg_merge.hxx>
14#include <awti_import.hxx>
15#include <insdel.h>
16#include <macros.hxx>
17#include <mod_rlimit.h>
18#include <TreeDisplay.hxx>
19
20#include <app.hxx>
21#include <sel_boxes.hxx>
22#include <TreeAwars.hxx>
23
24#include <aw_advice.hxx>
25#include <aw_question.hxx>
26#include <aw_awars.hxx>
27#include <aw_edit.hxx>
28#include <aw_file.hxx>
29#include <aw_msg.hxx>
30#include <aw_root.hxx>
31#include <aw_global_awars.hxx>
32#include <aw_system.hxx>
33#include <aw_color_groups.hxx>
34
35#include <adGene.h>
36
37#include <arb_version.h>
38#include <arb_progress.h>
39#include <arb_file.h>
40
41#include <signal.h>
42
43using namespace std;
44
45AW_HEADER_MAIN
46
47NT_global GLOBAL;
48
49// NT_format_all_alignments may be called after any operation which causes
50// unformatted alignments (e.g importing sequences)
51//
52// It tests all alignments whether they need to be formatted
53// and asks the user if they should be formatted.
54
55GB_ERROR NT_format_all_alignments(GBDATA *gb_main) {
56    GB_ERROR            err = NULp;
57    GB_transaction      ta(gb_main);
58    GB_topSecurityLevel unsecured(gb_main);
59
60    long ali_count = GBT_count_alignments(gb_main);
61    if (ali_count) {
62        arb_progress progress("Formatting alignments", ali_count);
63        err = GBT_check_data(gb_main, NULp);
64
65        AW_repeated_question question;
66        question.add_help("prompt/format_alignments.hlp");
67
68        GBDATA *gb_presets = GBT_get_presets(gb_main);
69        for (GBDATA *gb_ali = GB_entry(gb_presets, "alignment");
70             gb_ali && !err;
71             gb_ali        = GB_nextEntry(gb_ali))
72        {
73            GBDATA *gb_aligned = GB_search(gb_ali, "aligned", GB_INT);
74
75            if (GB_read_int(gb_aligned) == 0) { // sequences in alignment are not formatted
76                enum FormatAction {
77                    FA_ASK_USER   = 0, // ask user
78                    FA_FORMAT_ALL = 1, // format automatically w/o asking
79                    FA_SKIP_ALL   = 2, // skip automatically w/o asking
80                };
81                FormatAction  format_action = FA_ASK_USER;
82                GBDATA       *gb_ali_name   = GB_entry(gb_ali, "alignment_name");
83                const char   *ali_name      = GB_read_char_pntr(gb_ali_name);
84
85                {
86                    bool    is_ali_genom   = strcmp(ali_name, GENOM_ALIGNMENT) == 0;
87                    GBDATA *gb_auto_format = GB_entry(gb_ali, "auto_format");
88
89                    if (gb_auto_format) {
90                        format_action = FormatAction(GB_read_int(gb_auto_format));
91                        if (is_ali_genom) {
92                            if (format_action != FA_SKIP_ALL) {
93                                format_action = FA_SKIP_ALL; // always skip ali_genom
94                                err           = GB_write_int(gb_auto_format, FA_SKIP_ALL);
95                            }
96                        }
97                    }
98                    else if (is_ali_genom) {
99                        format_action = FA_SKIP_ALL;
100                        err           = GBT_write_int(gb_ali, "auto_format", FA_SKIP_ALL);  // always skip
101                    }
102                }
103
104                bool perform_format = false;
105                if (!err) {
106                    switch (format_action) {
107                        case FA_FORMAT_ALL: perform_format = true; break;
108                        case FA_SKIP_ALL:   perform_format = false; break;
109                        default: {
110                            char *qtext  = GBS_global_string_copy("Alignment '%s' is not formatted. Format?", ali_name);
111                            int   answer = question.get_answer("format_alignments", qtext, "Format,Skip,Always format,Always skip", "all", false);
112
113                            switch (answer) {
114                                case 2:
115                                    err = GBT_write_int(gb_ali, "auto_format", FA_FORMAT_ALL);
116                                    // fall-through
117                                case 0:
118                                    perform_format = true;
119                                    break;
120
121                                case 3:
122                                    err = GBT_write_int(gb_ali, "auto_format", FA_SKIP_ALL);
123                                    break;
124                            }
125
126                            free(qtext);
127                            break;
128                        }
129                    }
130                }
131                if (!err && perform_format) {
132                    err = ARB_format_alignment(gb_main, ali_name);
133                }
134            }
135            progress.inc_and_check_user_abort(err);
136        }
137
138        if (err) progress.done();
139    }
140
141    err = ta.close(err);
142    return err;
143}
144
145
146// --------------------------------------------------------------------------------
147
148
149static GB_ERROR nt_check_database_consistency() {
150    // called once on ARB_NTREE startup
151    arb_progress cons_progress("Checking consistency");
152
153    GB_ERROR err  = NT_format_all_alignments(GLOBAL.gb_main);
154    if (!err) err = NT_repair_DB(GLOBAL.gb_main);
155
156    return err;
157}
158
159__ATTR__USERESULT static GB_ERROR startup_mainwindow_and_dbserver(AW_root *aw_root, const char *autorun_macro, TREE_canvas*& result_ntw) {
160    AWT_initTreeAwarRegistry(GLOBAL.gb_main);
161
162    GB_ERROR error = configure_macro_recording(aw_root, "ARB_NT", GLOBAL.gb_main);
163    if (!error) {
164        result_ntw = NT_create_main_window(aw_root);
165        if (GB_is_server(GLOBAL.gb_main)) {
166            error = nt_check_database_consistency();
167            if (!error) NT_repair_userland_problems();
168        }
169    }
170
171    if (!error) {
172        error = GBK_system("arb_macro_compatibility.sh &"); // run async to avoid (DB) lock problems
173    }
174
175    if (!error && autorun_macro) execute_macro(aw_root, autorun_macro);
176
177    return error;
178}
179
180static ARB_ERROR load_and_startup_main_window(AW_root *aw_root, const char *autorun_macro) {
181    char *db_server = aw_root->awar(AWAR_DB_PATH)->read_string();
182    GLOBAL.gb_main  = GBT_open(db_server, "rw");
183
184    ARB_ERROR error;
185    if (!GLOBAL.gb_main) {
186        error = GB_await_error();
187    }
188    else {
189        aw_root->awar(AWAR_DB_PATH)->write_string(db_server);
190        aw_root->awar(AWAR_SAVED_DB_PATH)->write_string(db_server);
191
192#define MAXNAMELEN 35
193        int len = strlen(db_server);
194        if (len>MAXNAMELEN) {
195            char *nameOnly = strrchr(db_server, '/');
196            if (nameOnly) {
197                nameOnly++;
198                len -= (nameOnly-db_server);
199                memmove(db_server, nameOnly, len+1);
200                if (len>MAXNAMELEN) {
201                    strcpy(db_server+MAXNAMELEN-3, "...");
202                }
203            }
204        }
205#if defined(DEBUG)
206        AWT_announce_db_to_browser(GLOBAL.gb_main, GBS_global_string("ARB database (%s)", db_server));
207#endif // DEBUG
208
209        TREE_canvas *dummy   = NULp;
210        GB_ERROR     problem = startup_mainwindow_and_dbserver(aw_root, autorun_macro, dummy);
211        aw_message_if(problem); // no need to terminate ARB
212    }
213
214    free(db_server);
215    return error;
216}
217
218#define AWAR_DB_FILTER    AWAR_DBBASE "/filter"
219#define AWAR_DB_DIRECTORY AWAR_DBBASE "/directory"
220
221static void nt_delete_database(AW_window *aww) {
222    AW_root *aw_root   = aww->get_root();
223    char    *db_server = aw_root->awar(AWAR_DB_PATH)->read_string();
224
225    if (strlen(db_server)) {
226        if (aw_ask_sure(NULp, GBS_global_string("Are you sure to delete database %s\nNote: there is no way to undelete it afterwards", db_server))) {
227            GB_ERROR error = GB_delete_database(db_server);
228            if (error) {
229                aw_message(error);
230            }
231            else {
232                aw_root->awar(AWAR_DB_FILTER)->touch();
233            }
234        }
235    }
236    else {
237        aw_message("No database selected");
238    }
239    free(db_server);
240}
241
242static void start_main_window_after_import(AW_root *aw_root) {
243    GLOBAL.aw_root = aw_root;
244
245    GBDATA *gb_imported = AWTI_acquire_imported_DB_and_cleanup_importer(aw_root);
246    nt_assert(gb_imported == GLOBAL.gb_main); // import-DB should already be used as main-DB
247    GLOBAL.gb_main      = gb_imported;
248
249    TREE_canvas *ntw = NULp;
250    aw_message_if(startup_mainwindow_and_dbserver(aw_root, NULp, ntw));
251
252    if (aw_root->awar(AWAR_IMPORT_AUTOCONF)->read_int()) {
253        NT_create_config_after_import(ntw, true);
254    }
255
256    aw_root->awar(AWAR_TREE_REFRESH)->touch();
257}
258
259static void nt_intro_start_existing(AW_window *aw_intro) {
260    aw_intro->hide();
261    ARB_ERROR error = load_and_startup_main_window(aw_intro->get_root(), NULp);
262    nt_assert(contradicted(error, got_macro_ability(aw_intro->get_root())));
263    if (error) {
264        aw_intro->show();
265        aw_popup_ok(error.deliver());
266    }
267    else {
268        error.expect_no_error();
269    }
270}
271
272static void nt_intro_start_merge(AW_window *aw_intro) {
273    AW_root    *aw_root    = aw_intro->get_root();
274    const char *dir        = aw_root->awar(AWAR_DB_DIRECTORY)->read_char_pntr();
275    char       *merge_args = GBS_global_string_copy("'%s' '%s'", dir, dir);
276
277    NT_confirm_exit_restart_if(aw_root, EXIT_SUCCESS, merge_args); // call arb_ntree as merge-tool on exit
278}
279
280static void nt_intro_start_import(AW_window *aw_intro) {
281    aw_intro->hide();
282
283    AW_root *aw_root = aw_intro->get_root();
284    aw_root->awar_string(AWAR_DB_PATH)->write_string("noname.arb");
285
286    AWTI_open_import_window(aw_root, NULp, true, NULp, makeRootCallback(start_main_window_after_import));
287    AWTI_set_importDB_pointer(GLOBAL.gb_main);
288
289    nt_assert(got_macro_ability(aw_root));
290}
291
292static AW_window *nt_create_intro_window(AW_root *awr) {
293    AW_window_simple *aws = new AW_window_simple;
294    aws->init(awr, "ARB_INTRO", "ARB INTRO");
295    aws->load_xfig("arb_intro.fig");
296
297    aws->at("close");
298    aws->callback(makeWindowCallback(NT_confirm_exit, EXIT_SUCCESS));
299    aws->create_button("EXIT", "Exit", "x");
300
301    aws->at("help");
302    aws->callback(makeHelpCallback("arb_intro.hlp"));
303    aws->create_button("HELP", "HELP", "H");
304
305    AW_create_standard_fileselection(aws, AWAR_DBBASE);
306
307    aws->button_length(0);
308
309    aws->at("logo");
310    aws->create_button(NULp, "#logo.xpm");
311
312    aws->at("version");
313    aws->create_button(NULp, GBS_global_string("Version " ARB_VERSION "\n(" ARB_BUILD_DATE ")")); // version + date
314
315    aws->at("copyright");
316    aws->create_button(NULp, GBS_global_string("(C) 1993-" ARB_BUILD_YEAR));
317
318    aws->at("old");
319    aws->callback(nt_intro_start_existing);
320    aws->create_autosize_button("OPEN_SELECTED", "OPEN SELECTED", "O");
321
322    aws->at("del");
323    aws->callback(nt_delete_database);
324    aws->create_autosize_button("DELETE_SELECTED", "DELETE SELECTED");
325
326    aws->at("new_complex");
327    aws->callback(nt_intro_start_import);
328    aws->create_autosize_button("CREATE_AND_IMPORT", "CREATE AND IMPORT", "I");
329
330    aws->at("merge");
331    aws->callback(nt_intro_start_merge);
332    aws->create_autosize_button("MERGE_TWO_DATABASES", "MERGE TWO ARB DATABASES", "O");
333
334    aws->at("expert");
335    aws->create_toggle(AWAR_EXPERT);
336
337    return aws;
338}
339
340static void AWAR_DB_PATH_changed_cb(AW_root *awr) {
341    static bool avoid_recursion = false;
342
343    if (!avoid_recursion) {
344        LocallyModify<bool> flag(avoid_recursion, true);
345
346        char *value  = awr->awar(AWAR_SAVED_DB_PATH)->read_string();
347        char *lslash = strrchr(value, '/');
348
349        char *name = lslash ? lslash+1 : value;
350#if defined(DEBUG)
351        printf("writing '%s' to AWAR_DB_NAME\n", name);
352#endif // DEBUG
353        awr->awar(AWAR_DB_NAME)->write_string(name);
354
355        if (lslash) {               // update value of directory
356            lslash[0] = 0;
357            awr->awar(AWAR_DB_DIRECTORY)->write_string(value);
358            lslash[0] = '/';
359        }
360
361        free(value);
362    }
363}
364
365class NtreeCommandLine : virtual Noncopyable {
366    int               arg_count;
367    char const*const* args;
368
369    bool help_requested;
370    bool do_import;
371
372    const char *macro_name;
373
374public:
375    NtreeCommandLine(int argc_, char const*const* argv_)
376        : arg_count(argc_-1),
377          args(argv_+1),
378          help_requested(false),
379          do_import(false),
380          macro_name(NULp)
381    {}
382
383    void shift() { ++args; --arg_count; }
384
385    int free_args() const { return arg_count; }
386    const char *get_arg(int num) const { return num<arg_count ? args[num] : NULp; }
387
388    bool wants_help() const { return help_requested; }
389    bool wants_import() const { return do_import; }
390    bool wants_merge() const { return arg_count == 2; }
391
392    const char *autorun_macro() const { return macro_name; }
393
394    void print_help(FILE *out) const {
395        fprintf(out,
396                "\n"
397                "arb_ntree version " ARB_VERSION_DETAILED "\n"
398                "(C) 1993-" ARB_BUILD_YEAR " The ARB-project\n"
399                "http://www.arb-home.de/\n"
400                "(version build by: " ARB_BUILD_USER "@" ARB_BUILD_HOST ")\n"
401                "\n"
402                "Possible usage:\n"
403                "  arb_ntree               => start ARB (intro)\n"
404                "  arb_ntree DB            => start ARB with DB\n"
405                "  arb_ntree DB1 DB2       => merge from DB1 into DB2\n"
406                "  arb_ntree --import FILE => import FILE into new database (FILE may be a filemask)\n"
407                "\n"
408                "Additional arguments possible with command lines above:\n"
409                "  --execute macroname     => execute macro 'macroname' after startup\n"
410                "\n"
411                "Each DB argument may be one of the following:\n"
412                "  database.arb            -> use existing or new database\n"
413                \":\"                     -> connect to database of a running instance of ARB\n"
414                "  directory               -> prompt for databases inside directory\n"
415                "  filemask                -> also prompt for DB, but more specific (e.g. \"prot*.arb\")\n"
416                "\n"
417            );
418    }
419
420    ARB_ERROR parse() {
421        ARB_ERROR error;
422
423        while (!error && arg_count>0 && args[0][0] == '-') {
424            const char *opt = args[0]+1;
425            if (opt[0] == '-') ++opt; // accept '-' or '--'
426            if (strcmp(opt, "help") == 0 || strcmp(opt, "h") == 0) { help_requested = true; }
427            else if (strcmp(opt, "execute") == 0) { shift(); macro_name = *args; }
428            else if (strcmp(opt, "import") == 0) { do_import = true; }
429
430            // bunch of test switches to provoke various ways to terminate (see also http://bugs.arb-home.de/ticket/538)
431            else if (strcmp(opt, "crash") == 0) { GBK_terminate("crash requested"); }
432            else if (strcmp(opt, "trap") == 0) { arb_assert(0); }
433            else if (strcmp(opt, "sighup") == 0) { raise(SIGHUP); }
434            else if (strcmp(opt, "sigsegv") == 0) { raise(SIGSEGV); }
435            else if (strcmp(opt, "sigterm") == 0) { raise(SIGTERM); }
436            else if (strcmp(opt, "fail") == 0) { exit(EXIT_FAILURE); }
437            else if (strcmp(opt, "exit") == 0) { exit(EXIT_SUCCESS); }
438            // end of test switches ----------------------------------------
439
440            else error = GBS_global_string("Unknown switch '%s'", args[0]);
441            shift();
442        }
443        // non-switch arguments remain in arg_count/args
444        if (!error) {
445            if (do_import && arg_count != 1) error = "expected exactly one file-name or file-mask behind --import";
446            else if (arg_count>2)            error = "too many stray arguments given (max. 2 accepted)";
447        }
448        return error;
449    }
450};
451
452enum ArgType { // args that might be specified on command line (for DB or FILE; see above)
453    RUNNING_DB,
454    DIRECTORY,
455    EXISTING_DB,
456    NEW_DB,
457    FILEMASK,
458    EXISTING_FILE,
459    UNKNOWN_ARG,
460};
461
462inline bool has_arb_suffix(const char *arg) {
463    const char *suffix = strstr(arg, ".arb");
464    if (suffix) {
465        return strcmp(suffix, ".arb") == 0;
466    }
467    return false;
468}
469
470static ArgType detectArgType(const char *arg) {
471    if (strcmp(arg, ":") == 0) return RUNNING_DB;
472    if (GB_is_directory(arg)) return DIRECTORY;
473    if (strpbrk(arg, "*?")) return FILEMASK;
474    if (has_arb_suffix(arg)) {
475        return GB_is_regularfile(arg) ? EXISTING_DB : NEW_DB;
476    }
477
478    GB_ERROR load_file_err = GBT_check_arb_file(arg);
479    if (!load_file_err) return EXISTING_DB;
480
481    return GB_is_regularfile(arg) ? EXISTING_FILE : UNKNOWN_ARG;
482}
483
484enum RunMode { NORMAL, IMPORT, MERGE, BROWSE };
485
486#define ABORTED_BY_USER "aborted by user"
487
488static ARB_ERROR check_argument_for_mode(const char *database, char *&browser_startdir, RunMode& mode) {
489    // Check whether 'database' is a
490    // - ARB database
491    // - directory name
492    // - file to import
493    //
494    // Modify 'mode' occordingly.
495    // Set 'browser_startdir'
496
497    ARB_ERROR error;
498    if (mode != IMPORT) {
499        if (!database) mode = BROWSE;
500        else {
501            GB_ERROR load_file_err = GBT_check_arb_file(database);
502            if (load_file_err) {
503                char *full_path = AW_unfold_path("PWD", database);
504
505                enum Action { LOAD_DB, CONVERT_DB, BROWSE_DB, EXIT, NOIDEA };
506                Action action = NOIDEA;
507                if (GB_is_directory(full_path)) {
508                    action = BROWSE_DB;
509                }
510                else {
511                    if (!GB_is_regularfile(full_path)) {
512                        const char *msg = GBS_global_string("'%s' is neither a known option nor a legal file- or directory-name.\n(Error: %s)",
513                                                            full_path, load_file_err);
514
515                        int ans = aw_question(NULp, msg, "Browser,Exit");
516                        action  = ans ? EXIT : BROWSE_DB;
517                    }
518                    else {
519                        const char *msg = GBS_global_string("Your file is not an original arb file\n(%s)", load_file_err);
520                        action          = (Action)aw_question(NULp, msg, "Continue (dangerous),Start Converter,Browser,Exit");
521                    }
522                }
523
524                switch (action) {
525                    case CONVERT_DB:    mode = IMPORT; break;
526                    case LOAD_DB:       break;
527                    case NOIDEA:        nt_assert(0); FALLTHROUGH; // in NDEBUG
528                    case EXIT:          error = ABORTED_BY_USER; break;
529                    case BROWSE_DB: {
530                        char *dir = nulldup(full_path);
531                        while (dir && !GB_is_directory(dir)) freeset(dir, AW_extract_directory(dir));
532
533                        if (dir) {
534                            nt_assert(GB_is_directory(dir));
535                            reassign(browser_startdir, dir);
536                            mode = BROWSE;
537                        }
538                        free(dir);
539                        break;
540                    }
541
542                }
543                free(full_path);
544            }
545        }
546    }
547
548    return error;
549}
550
551class SelectedDatabase : virtual Noncopyable {
552    GBDATA*&  gb_main;
553    char     *name;
554    ArgType   type;
555    char     *role;
556
557    void fix_name() {
558        if (type != RUNNING_DB && name[0]) {
559            const char *changed;
560
561            if (type == NEW_DB) {
562                changed = GB_unfold_in_directory(GB_getcwd(), name);
563            }
564            else {
565                changed = GB_canonical_path(name);
566            }
567
568            if (strcmp(name, changed) != 0) {
569                reselect_file(changed);
570            }
571        }
572    }
573
574public:
575    SelectedDatabase(GBDATA*& gb_main_, const char *name_, const char *role_)
576        : gb_main(gb_main_),
577          name(ARB_strdup(name_)),
578          type(detectArgType(name)),
579          role(ARB_strdup(role_))
580    {
581        fix_name();
582    }
583    ~SelectedDatabase() {
584        free(role);
585        free(name);
586    }
587
588    ArgType arg_type() const { return type; }
589
590    bool needs_to_prompt() const {
591        return type == DIRECTORY || type == FILEMASK || type == UNKNOWN_ARG;
592    }
593
594    const char *get_fullname() const { return name; }
595    const char *get_nameonly() const {
596        const char *slash = strrchr(name, '/');
597        return slash ? slash+1 : name;
598    }
599    const char *get_role() const { return role; }
600    const char *get_description() const { return GBS_global_string("%s (%s)", get_role(), get_nameonly()); }
601
602    ArgType get_type() const { return type; }
603
604    const char *get_dir() const {
605        const char *dir = NULp;
606        switch (type) {
607            case DIRECTORY:
608                dir = get_fullname();
609                break;
610
611            case NEW_DB:
612            case FILEMASK:
613            case EXISTING_FILE:
614            case EXISTING_DB: {
615                char *dir_copy;
616                GB_split_full_path(name, &dir_copy, NULp, NULp, NULp);
617
618                static SmartCharPtr dir_store;
619                dir_store = dir_copy;
620
621                dir = dir_copy;
622                break;
623            }
624            case UNKNOWN_ARG:
625            case RUNNING_DB:
626                dir = ".";
627                break;
628        }
629        return dir;
630    }
631
632    const char *get_mask() const {
633        const char *mask;
634        switch (type) {
635            case FILEMASK: {
636                char *mask_copy;
637                GB_split_full_path(name, NULp, &mask_copy, NULp, NULp);
638
639                static SmartCharPtr mask_store;
640                mask_store = mask_copy;
641
642                mask = mask_copy;
643                break;
644            }
645            default:
646                mask = ".arb";
647                break;
648        }
649        return mask;
650    }
651
652    void reselect_file(const char *file) {
653        freedup(name, file);
654        type = detectArgType(name);
655        fix_name();
656    }
657    void reselect_from_awar(AW_root *aw_root, const char *awar_name) {
658        if (awar_name) {
659            const char *file = aw_root->awar(awar_name)->read_char_pntr();
660            if (file) reselect_file(file);
661        }
662    }
663
664    GB_ERROR open_db_for_merge(bool is_source_db);
665    void close_db() { if (gb_main) GB_close(gb_main); }
666};
667
668GB_ERROR SelectedDatabase::open_db_for_merge(bool is_source_db) {
669    GB_ERROR    error    = NULp;
670    const char *openMode = "rw";
671
672    switch (get_type()) {
673        case DIRECTORY:
674        case FILEMASK:
675            GBK_terminate("Program logic error (should have been prompted for explicit DB name)");
676            break;
677
678        case UNKNOWN_ARG:
679        case EXISTING_FILE:
680            error = GBS_global_string("'%s' is no arb-database", get_fullname());
681            break;
682
683        case NEW_DB:
684            if (is_source_db) {
685                error = GBS_global_string("'%s' has to exist", get_fullname());
686                break;
687            }
688            openMode = "crw"; // allow to create DB
689            break;
690
691        case EXISTING_DB:
692        case RUNNING_DB:
693            break;
694    }
695
696    if (!error) {
697        gb_main             = GBT_open(get_fullname(), openMode);
698        if (!gb_main) error = GB_await_error();
699        else {
700            IF_DEBUG(AWT_announce_db_to_browser(gb_main, get_description()));
701        }
702    }
703
704    return error;
705}
706
707struct merge_scheme : virtual Noncopyable {
708    SelectedDatabase *src;
709    SelectedDatabase *dst;
710
711    char *awar_src;
712    char *awar_dst;
713
714    GB_ERROR error;
715
716    merge_scheme(SelectedDatabase *src_, SelectedDatabase *dst_)
717        : src(src_),
718          dst(dst_),
719          awar_src(NULp),
720          awar_dst(NULp),
721          error(NULp)
722    {}
723    ~merge_scheme() {
724        if (error) {
725            src->close_db();
726            dst->close_db();
727        }
728        delete src;
729        delete dst;
730        free(awar_src);
731        free(awar_dst);
732    }
733
734
735    void open_dbs() {
736        if (!error) error = src->open_db_for_merge(true);
737        if (!error) error = dst->open_db_for_merge(false);
738    }
739
740    void fix_dst(AW_root *aw_root) { dst->reselect_from_awar(aw_root, awar_dst); }
741    void fix_src(AW_root *aw_root) { src->reselect_from_awar(aw_root, awar_src); }
742
743    bool knows_dbs() const { return !src->needs_to_prompt() && !dst->needs_to_prompt(); }
744};
745
746static bool merge_tool_running_as_client = true; // default to safe state (true avoids call of 'arb_clean' at exit)
747__ATTR__NORETURN static void exit_from_merge(const char *restart_args) {
748    // if 'restart_args' is null (or an empty string) => do not restart
749    //
750    // Note: macro will already have terminated here (see logic in MG_confirm_exit_restart_if)
751
752    if (merge_tool_running_as_client) { // there is a main ARB running
753        NT_start_if(restart_args);
754        exit(EXIT_SUCCESS); // exit w/o killing clients (in contrast to else-branch)
755    }
756    else {
757        // Note: this branch also kills all client processes
758        NT_exit_now_restart_if(AW_root::SINGLETON, EXIT_SUCCESS, restart_args);
759    }
760    // expected to be never reached. Anyway: caller chain will terminate
761    // (in MacroExitor::perform_exit_or_terminate) if this function returns.
762}
763
764static void merge_startup_abort_cb(AW_window*) {
765    fputs("Error: merge aborted by user\n", stderr);
766    exit_from_merge(NULp);
767}
768
769static AW_window *merge_startup_error_window(AW_root *aw_root, GB_ERROR error) {
770    AW_window_simple *aw_msg = new AW_window_simple;
771
772    aw_msg->init(aw_root, "arb_merge_error", "ARB merge error");
773    aw_msg->recalc_size_atShow(AW_RESIZE_DEFAULT); // force size recalc (ignores user size)
774
775    aw_msg->at(10, 10);
776    aw_msg->auto_space(10, 10);
777
778    aw_msg->create_autosize_button(NULp, error, "", 2);
779    aw_msg->at_newline();
780
781    aw_msg->callback(merge_startup_abort_cb);
782    aw_msg->create_autosize_button("OK", "Ok", "O", 2);
783
784    aw_msg->window_fit();
785
786    return aw_msg;
787}
788static AW_window *startup_merge_main_window(AW_root *aw_root, merge_scheme *ms, const char *autorun_macro) {
789    ms->fix_src(aw_root);
790    ms->fix_dst(aw_root);
791
792    if (ms->knows_dbs()) {
793        ms->open_dbs();
794    }
795    else {
796        ms->error = "Need two databases to merge";
797    }
798
799    AW_window *aw_result;
800    if (!ms->error) {
801        MERGE_create_db_file_awars(aw_root, AW_ROOT_DEFAULT, ms->src->get_fullname(), ms->dst->get_fullname());
802        AW_init_color_group_defaults("arb_ntree");
803
804        bool dst_is_new = ms->dst->arg_type() == NEW_DB;
805        aw_result       = MERGE_create_main_window(aw_root, dst_is_new, exit_from_merge);
806
807        nt_assert(got_macro_ability(aw_root));
808
809        if (autorun_macro) {
810            execute_macro(aw_root, autorun_macro);
811        }
812    }
813    else {
814        aw_result = merge_startup_error_window(aw_root, ms->error);
815    }
816    delete ms; // was allocated in startup_gui()
817    return aw_result;
818}
819
820static AW_window *startup_merge_prompting_for_nonexplicit_dst_db(AW_root *aw_root, merge_scheme *ms, const char *autorun_macro) {
821    AW_window *aw_result;
822    if (ms->dst->needs_to_prompt()) {
823        aw_result = awt_create_load_box(aw_root, "Select", ms->dst->get_role(),
824                                        ms->dst->get_dir(), ms->dst->get_mask(),
825                                        &(ms->awar_dst),
826                                        AW_window::makeWindowReplacer(makeCreateWindowCallback(startup_merge_main_window, ms, autorun_macro)),
827                                        makeWindowCallback(merge_startup_abort_cb), "Cancel");
828    }
829    else {
830        aw_result = startup_merge_main_window(aw_root, ms, autorun_macro);
831    }
832    return aw_result;
833}
834
835static AW_window *startup_merge_prompting_for_nonexplicit_dbs(AW_root *aw_root, merge_scheme *ms, const char *autorun_macro) {
836    // if src_spec or dst_spec needs to be prompted for -> startup prompters with callbacks bound to continue
837    // otherwise just startup merge
838
839    AW_window *aw_result;
840    if (ms->src->needs_to_prompt()) {
841        aw_result = awt_create_load_box(aw_root, "Select", ms->src->get_role(),
842                                        ms->src->get_dir(), ms->src->get_mask(),
843                                        &(ms->awar_src),
844                                        AW_window::makeWindowReplacer(makeCreateWindowCallback(startup_merge_prompting_for_nonexplicit_dst_db, ms, autorun_macro)),
845                                        makeWindowCallback(merge_startup_abort_cb), "Cancel");
846    }
847    else {
848        aw_result = startup_merge_prompting_for_nonexplicit_dst_db(aw_root, ms, autorun_macro);
849    }
850    return aw_result;
851}
852
853static void startup_gui(NtreeCommandLine& cl, ARB_ERROR& error) {
854    {
855        char *message = ARB_strdup(GB_path_in_ARBLIB("message"));
856        char *stamp   = ARB_strdup(GB_path_in_arbprop("msgtime"));
857        if (GB_time_of_file(message)>GB_time_of_file(stamp)) {
858            AW_edit(message);
859            AW_system(GBS_global_string("touch %s", stamp));
860        }
861        free(stamp);
862        free(message);
863    }
864
865    // create some early awars
866    // Note: normally you don't like to add your awar-init-function here, but into nt_create_all_awars()
867
868    AW_root* aw_root = GLOBAL.aw_root;
869
870    AW_create_fileselection_awars(aw_root, AWAR_DBBASE, "", ".arb", "noname.arb"); // creates AWAR_DB_PATH
871    aw_root->awar_string(AWAR_DB_NAME, "noname.arb");
872
873    aw_root->awar_int(AWAR_EXPERT, 0);
874
875    AWT_install_cb_guards();
876
877    ARB_declare_global_awars(aw_root, AW_ROOT_DEFAULT);
878    aw_root->awar(AWAR_SAVED_DB_PATH)->add_callback(AWAR_DB_PATH_changed_cb);
879
880    aw_root->setUserActionTracker(new NullTracker); // no macro recording inside prompters that may popup
881    if (!error) {
882        nt_assert(!cl.wants_help());
883
884        RunMode mode = NORMAL;
885
886        if      (cl.wants_merge())  mode = MERGE;
887        else if (cl.wants_import()) mode = IMPORT;
888
889        if (mode == MERGE) {
890            MERGE_create_all_awars(aw_root, AW_ROOT_DEFAULT);
891
892            merge_scheme *ms = new merge_scheme( // freed in startup_merge_main_window()
893                new SelectedDatabase(GLOBAL_gb_src, cl.get_arg(0), "Source DB for merge"),
894                new SelectedDatabase(GLOBAL_gb_dst, cl.get_arg(1), "Destination DB for merge"));
895
896            merge_tool_running_as_client = ms->src->arg_type() == RUNNING_DB || ms->dst->arg_type() == RUNNING_DB;
897
898            if (ms->src->arg_type() == RUNNING_DB && ms->dst->arg_type() == RUNNING_DB) {
899                error = "You cannot merge from running to running DB";
900            }
901            else {
902                aw_root->setUserActionTracker(new NullTracker); // no macro recording during startup of merge tool (file prompts)
903                AW_window *aww = startup_merge_prompting_for_nonexplicit_dbs(aw_root, ms, cl.autorun_macro());
904
905                nt_assert(contradicted(aww, error));
906
907                if (!error) {
908                    aww->show();
909                    aw_root->main_loop();
910                    nt_assert(0);
911                }
912            }
913        }
914        else {
915            const char *database         = NULp;
916            char       *browser_startdir = ARB_strdup(".");
917
918            if (cl.free_args() > 0) database = cl.get_arg(0);
919
920            error = check_argument_for_mode(database, browser_startdir, mode);
921            if (!error) {
922                if (mode == IMPORT) {
923                    AWTI_open_import_window(aw_root, database, true, NULp, makeRootCallback(start_main_window_after_import));
924                    AWTI_set_importDB_pointer(GLOBAL.gb_main);
925
926                    nt_assert(got_macro_ability(aw_root));
927                    aw_root->main_loop();
928                }
929                else if (mode == NORMAL) {
930                    aw_root->awar(AWAR_DB_PATH)->write_string(database);
931                    error = load_and_startup_main_window(aw_root, cl.autorun_macro());
932                    if (!error) {
933                        nt_assert(got_macro_ability(aw_root));
934                        aw_root->main_loop();
935                    }
936                }
937                else if (mode == BROWSE) {
938                    aw_root->awar(AWAR_DB_DIRECTORY)->write_string(browser_startdir);
939                    char *latest = GB_find_latest_file(browser_startdir, "/\\.(arb|a[0-9]{2})$/");
940                    if (latest) {
941                        int l = strlen(latest);
942                        strcpy(latest+l-3, "arb");
943                        aw_root->awar(AWAR_DB_PATH)->write_string(latest);
944                        free(latest);
945                    }
946                    nt_create_intro_window(aw_root)->show();
947                    aw_root->setUserActionTracker(new NullTracker); // no macro recording inside intro window
948                    aw_root->main_loop();
949                }
950            }
951            free(browser_startdir);
952        }
953    }
954
955    if (error) {
956        const char *msg = error.preserve();
957        if (strcmp(msg, ABORTED_BY_USER) != 0) aw_popup_ok(msg);
958    }
959}
960
961int ARB_main(int argc, char *argv[]) {
962    {
963        // i really dont want 'arb_ntree --help' to startup the GUI
964        // hack: parse CL twice
965        NtreeCommandLine cl(argc, argv);
966        bool             cl_ok = !cl.parse().deliver();
967        if (cl_ok) {
968            if (cl.wants_help()) {
969                cl.print_help(stderr);
970                return EXIT_SUCCESS;
971            }
972        }
973    }
974
975    aw_initstatus();
976    GB_set_verbose();
977
978    GB_shell shell;
979    AW_root *aw_root = AWT_create_root("ntree.arb", "ARB_NT", need_macro_ability());
980
981    GLOBAL.aw_root = aw_root;
982
983    NtreeCommandLine cl(argc, argv);
984    ARB_ERROR        error = cl.parse();
985
986    if (!error) {
987        if (cl.wants_help()) {
988            cl.print_help(stderr);
989        }
990        else {
991            ModRLimit increase_stacksize(RLIMIT_STACK, TREEDISP_STACKSIZE);
992            startup_gui(cl, error);
993        }
994    }
995
996    int exitcode = EXIT_SUCCESS;
997    if (error) {
998        exitcode = EXIT_FAILURE;
999        fprintf(stderr, "arb_ntree: Error: %s\n", error.deliver());
1000    }
1001    else {
1002        error.expect_no_error();
1003    }
1004
1005    delete aw_root;
1006
1007    return exitcode;
1008}
1009
Note: See TracBrowser for help on using the repository browser.