source: trunk/SL/AW_NAME/AW_rename.cxx

Last change on this file was 19206, 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: 24.8 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : AW_rename.cxx                                     //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "AW_rename.hxx"
12
13#include <aw_awars.hxx>
14#include <aw_window.hxx>
15#include <aw_root.hxx>
16#include <aw_question.hxx>
17#include <aw_msg.hxx>
18#include <arb_progress.h>
19
20#include <names_client.h>
21#include <servercntrl.h>
22#include <client.h>
23
24#include <cctype>
25#include <ctime>
26
27static const char *get_addid(GBDATA *gb_main) {
28    GB_transaction ta(gb_main);
29    GBDATA *gb_addid = GB_search(gb_main, AWAR_NAMESERVER_ADDID, GB_FIND);
30    return gb_addid ? GB_read_char_pntr(gb_addid) : NULp;
31}
32
33static GB_ERROR set_addid(GBDATA *gb_main, const char *addid) {
34    GB_ERROR error    = GB_push_transaction(gb_main);
35    if (!error) error = GBT_write_string(gb_main, AWAR_NAMESERVER_ADDID, null2empty(addid));
36    return GB_end_transaction(gb_main, error);
37}
38
39const char *AW_get_nameserver_addid(GBDATA *gb_main) {
40    // return the additional field used for nameserver connection
41    const char *addid = get_addid(gb_main);
42
43    aw_assert(addid); // the current DB has no entry AWAR_NAMESERVER_ADDID! (programmers error)
44    if (!addid) addid = ""; // NDEBUG fallback
45
46    return addid;
47}
48
49GB_ERROR AW_select_nameserver(GBDATA *gb_main, GBDATA *gb_other_main) {
50    // if entry AWAR_NAMESERVER_ADDID isn't defined yet, try to detect a reasonable value
51    // from arb_tcp.dat. Ask user if multiple servers are defined.
52    //
53    // if gb_other_main is defined try to use value from there.
54
55    const char *addid   = get_addid(gb_main);
56    GB_ERROR    error   = NULp;
57
58    if (!addid && gb_other_main && gb_other_main != gb_main) {
59        // look whether main DB has a defined addid
60        addid = get_addid(gb_other_main);
61        set_addid(gb_main, addid);
62    }
63
64    if (!addid) {
65        const char * const *nameservers = GBS_get_arb_tcp_entries("ARB_NAME_SERVER*");
66
67        if (!nameservers) error = GB_await_error();
68        else {
69            int serverCount = 0;
70
71            for (int c = 0; nameservers[c]; c++) serverCount++;
72
73            if (serverCount == 0) {
74                error = GBS_global_string("No nameserver defined.");
75            }
76            else {
77                char **fieldNames = ARB_alloc<char*>(serverCount);
78                for (int c = 0; c<serverCount; c++) {
79                    const char *ipport = GBS_read_arb_tcp(nameservers[c]);
80                    if (!ipport) {
81                        error = GB_await_error();
82                        fieldNames[c] = NULp;
83                    }
84                    else {
85                        fieldNames[c] = nulldup(GBS_scan_arb_tcp_param(ipport, "-f"));      // may return 0
86
87                        // parameter -f contains default value (e.g. '-fstart=1')
88                        if (fieldNames[c]) {
89                            char *equal = strchr(fieldNames[c], '=');
90                            if (equal) equal[0] = 0;
91                        }
92                    }
93                }
94
95                if (!error) {
96                    if (serverCount == 1) { // exactly 1 server defined -> don't ask
97                        error = set_addid(gb_main, fieldNames[0]);
98                    }
99                    else { // let the user select which nameserver to use
100                        aw_assert(serverCount>1);
101
102                        int         len     = serverCount; // commas+0term
103                        const char *nofield = "None (only 'acc')";
104
105                        for (int c = 0; c<serverCount; c++) {
106                            if (fieldNames[c]) len += strlen(fieldNames[c]);
107                            else len += strlen(nofield);
108                        }
109
110                        char *buttons = ARB_alloc<char>(len);
111                        buttons[0]    = 0;
112                        for (int c = 0; c<serverCount; c++) {
113                            if (c) strcat(buttons, ",");
114                            strcat(buttons, fieldNames[c] ? fieldNames[c] : nofield);
115                        }
116
117                        int answer = aw_question("nameserv_select",
118                                                 "Select if and which additional DB field you want to use",
119                                                 buttons, false, "namesadmin.hlp");
120
121                        error = set_addid(gb_main, fieldNames[answer]);
122
123                        free(buttons);
124                    }
125                }
126
127                for (int c = 0; c<serverCount; c++) free(fieldNames[c]);
128                free(fieldNames);
129            }
130        }
131    }
132
133    return error;
134}
135
136
137// ------------------------------------
138//      class NameServerConnection
139
140class NameServerConnection : virtual Noncopyable {
141    aisc_com   *link;
142    T_AN_LOCAL  locs;
143    T_AN_MAIN   com;
144    int         persistent;     // if true -> connection will not be closed
145    time_t      linktime;       // time, when link has been established
146
147    int init_local_com_names()
148    {
149        if (!link) return 1;    //!* create and init local com structure **
150        if (aisc_create(link, AN_MAIN, com,
151                        MAIN_LOCAL, AN_LOCAL, locs,
152                        LOCAL_WHOAMI, "i bin der arb_tree",
153                        NULp)) {
154            return 1;
155        }
156        return 0;
157    }
158
159    GB_ERROR reconnect(GBDATA *gb_main) { // reconnect ignoring consistency
160        int old_persistent = persistent;
161
162        printf("Reconnecting name server\n");
163
164        persistent = 0; // otherwise disconnect() won't disconnect
165        disconnect();
166        persistent = old_persistent; // restore previous persistence
167
168        return connect(gb_main);
169    }
170
171    char *fieldUsedByServer(GB_ERROR& err) {
172        char *field = NULp;
173        if (aisc_get(link, AN_MAIN, com,
174                     MAIN_ADD_FIELD, &field,
175                     NULp)) {
176            err = "Connection Problems with the NAME_SERVER";
177            aw_assert(!field);
178        }
179        return field;
180    }
181
182    GB_ERROR expectServerUsesField(const char *expected_field) {
183        GB_ERROR  err          = NULp;
184        char     *server_field = fieldUsedByServer(err);
185
186        if (!err && strcmp(expected_field, server_field) != 0) {
187            err = GBS_global_string("Additional field doesn't match (expected='%s', server uses='%s')", expected_field, server_field);
188        }
189        free(server_field);
190        return err;
191    }
192
193public:
194
195    NameServerConnection() {
196        link       = NULp;
197        locs.clear();
198        com.clear();
199        persistent = 0;
200    }
201    virtual ~NameServerConnection() {
202        aw_assert(persistent == 0); // forgot to remove persistence ?
203        disconnect();
204    }
205
206    GB_ERROR connect(GBDATA *gb_main) {
207        aw_assert(!GB_have_error());
208
209        arb_progress::show_comment("Connecting to name server");
210
211        GB_ERROR err = NULp;
212        if (!link) {
213            const char *add_field = AW_get_nameserver_addid(gb_main);
214            const char *server_id = GBS_nameserver_tag(add_field);
215
216            err = arb_look_and_start_server(AISC_MAGIC_NUMBER, server_id);
217
218            if (!err) {
219                const char *ipport = GBS_read_arb_tcp(server_id);
220                if (!ipport) err = GB_await_error();
221                else {
222                    link     = aisc_open(ipport, com, AISC_MAGIC_NUMBER, &err);
223                    linktime = time(NULp);
224
225                    if (!err) {
226                        if (init_local_com_names()) {
227                            err = GBS_global_string("Can't connect %s %s", server_id, ipport);
228                        }
229                        else {
230                            err = expectServerUsesField(add_field);
231                        }
232                    }
233                }
234            }
235        }
236        else {
237            long linkAge     = int(time(NULp)-linktime);
238            bool doReconnect = false;
239
240#if defined(DEBUG) && 0
241            // print information about name-server link age
242            static long lastage = -1;
243            if (linkAge != lastage) {
244                printf("Age of NameServerConnection: %li\n", linkAge);
245                lastage = linkAge;
246            }
247#endif // DEBUG
248
249            if (linkAge > (5*60)) { // perform a reconnect after 5 minutes
250                // Reason : The pipe to the name server breaks after some time
251                doReconnect = true;
252            }
253            else {
254                const char *add_field = AW_get_nameserver_addid(gb_main);
255                GB_ERROR    error     = expectServerUsesField(add_field);
256
257                if (error) {
258                    printf("Error: %s\n", error);
259                    doReconnect = true;
260                }
261            }
262
263            if (doReconnect) {
264                err = reconnect(gb_main);
265            }
266        }
267        aw_assert(!GB_have_error());
268        return err;
269    }
270
271    void disconnect() {
272        if (persistent == 0) {
273            if (link) {
274                aisc_close(link, com);
275                locs.clear();
276                com.clear();
277            }
278            link = NULp;
279        }
280    }
281
282    void persistence(bool persist) {
283        if (persist) {
284            ++persistent;
285        }
286        else {
287            --persistent;
288            if (persistent <= 0) {
289                persistent = 0;
290                disconnect();
291            }
292        }
293    }
294
295
296    aisc_com *getLink() { return link; }
297    const T_AN_LOCAL& getLocs() const { return locs; }
298};
299
300static NameServerConnection name_server;
301
302PersistentNameServerConnection::PersistentNameServerConnection() {
303    name_server.persistence(true);
304}
305PersistentNameServerConnection::~PersistentNameServerConnection() {
306    name_server.persistence(false);
307}
308
309// --------------------------------------------------------------------------------
310
311GB_ERROR AW_test_nameserver(GBDATA *gb_main) {
312    return name_server.connect(gb_main);
313}
314
315// --------------------------------------------------------------------------------
316
317GB_ERROR AWTC_generate_one_name(GBDATA *gb_main, const char *full_name, const char *acc, const char *addid, char*& new_name) {
318    // create a unique short name for 'full_name'
319    // the result is written into 'new_name' (as malloc-copy)
320    // if fails: GB_ERROR!=0 && new_name==0
321    // acc and addid may be 0
322
323    new_name = NULp;
324    if (!acc) acc = "";
325
326    arb_progress progress("Generating species ID");
327
328    GB_ERROR err = name_server.connect(gb_main);
329    if (err) return err;
330
331    static char *shrt = NULp;
332    if (strlen(full_name)) {
333        if (aisc_nput(name_server.getLink(), AN_LOCAL, name_server.getLocs(),
334                      LOCAL_FULL_NAME,  full_name,
335                      LOCAL_ACCESSION,  acc,
336                      LOCAL_ADDID,      null2empty(addid),
337                      LOCAL_ADVICE,     "",
338                      NULp)) {
339            err = "Connection Problems with the NAME_SERVER";
340        }
341        if (aisc_get(name_server.getLink(), AN_LOCAL, name_server.getLocs(),
342                     LOCAL_GET_SHORT,   &shrt,
343                     NULp)) {
344            err = "Connection Problems with the NAME_SERVER";
345        }
346    }
347
348    if (err) {
349        free(shrt);
350    }
351    else {
352        if (shrt) {
353            new_name = shrt;
354            shrt = NULp;
355        }
356        else {
357            err = GB_export_errorf("Failed to generate species ID (shortname) for '%s'", full_name);
358        }
359    }
360
361    name_server.disconnect();
362
363    return err;
364}
365
366GB_ERROR AWTC_recreate_name(GBDATA *gb_species) {
367    GBDATA       *gb_main = GB_get_root(gb_species);
368    arb_progress  progress("Recreating species ID");
369    GB_ERROR      error   = name_server.connect(gb_main);
370
371    if (!error) {
372        const char *add_field = AW_get_nameserver_addid(gb_main);
373        char       *ali_name  = GBT_get_default_alignment(gb_main);
374
375        if (!ali_name) {
376            error = GB_await_error();
377        }
378        else {
379            GBDATA *gb_name      = GB_entry(gb_species, "name");
380            GBDATA *gb_full_name = GB_entry(gb_species, "full_name");
381            GBDATA *gb_acc       = GBT_gen_accession_number(gb_species, ali_name);
382            GBDATA *gb_addfield  = add_field[0] ? GB_entry(gb_species, add_field) : NULp;
383
384            char *name      = gb_name      ? GB_read_string   (gb_name)      : strdup("");
385            char *full_name = gb_full_name ? GB_read_string   (gb_full_name) : strdup("");
386            char *acc       = gb_acc       ? GB_read_string   (gb_acc)       : strdup("");
387            char *addid     = gb_addfield  ? GB_read_as_string(gb_addfield)  : strdup("");
388
389            long  deleted = 0;
390            char *shrt    = NULp;
391
392            if (aisc_nput(name_server.getLink(), AN_LOCAL, name_server.getLocs(),
393                          LOCAL_FULL_NAME,  full_name,
394                          LOCAL_ACCESSION,  acc,
395                          LOCAL_ADDID,      addid,
396                          LOCAL_ADVICE,     "",
397                          NULp) != 0 ||
398                aisc_get(name_server.getLink(), AN_LOCAL, name_server.getLocs(),
399                         LOCAL_DEL_SHORT,   &deleted,
400                         NULp)  != 0 ||
401                aisc_get(name_server.getLink(), AN_LOCAL, name_server.getLocs(),
402                         LOCAL_GET_SHORT,   &shrt,
403                         NULp)  != 0)
404            {
405                error = "Connection Problems with the NAME_SERVER";
406            }
407            name_server.disconnect();
408
409            if (!error) {
410                GBT_begin_rename_session(gb_main, 0);
411                error = GBT_rename_species(name, shrt, true);
412                if (error) {
413                    if (GBT_find_species(gb_main, shrt)) { // it was a rename error
414                        int done = 0;
415                        error    = NULp;
416                        for (int count = 2; !done && !error && count<10; count++) {
417                            const char *other_short = GBS_global_string("%s.%i", shrt, count);
418                            if (!GBT_find_species(gb_main, other_short)) {
419                                error            = GBT_rename_species(name, other_short, true);
420                                if (!error) done = 1;
421                            }
422                        }
423
424                        if (!done && !error) {
425                            error = "Failed to regenerate species ID. Please use 'Species/Synchronize IDs'";
426                        }
427                    }
428                }
429
430                if (error) GBT_abort_rename_session();
431                else error = GBT_commit_rename_session();
432            }
433
434            free(shrt);
435            free(addid);
436            free(acc);
437            free(full_name);
438            free(name);
439            free(ali_name);
440        }
441    }
442
443    return error;
444}
445
446char *AWTC_create_numbered_suffix(GB_HASH *species_name_hash, const char *shortname, GB_ERROR& warning) {
447    char *newshort = NULp;
448    if (GBS_read_hash(species_name_hash, shortname)) {
449        int i;
450        ARB_alloc(newshort, strlen(shortname)+20);
451        for (i = 1; ; i++) {
452            sprintf(newshort, "%s.%i", shortname, i);
453            if (!GBS_read_hash(species_name_hash, newshort))break;
454        }
455
456        warning =
457            "There are duplicated species!\n"
458            "The IDs of these species ('name') contain a '.' character followed by a number.\n"
459            "We strongly recommend you try understand and solve this problem\n"
460            "(see HELP in 'Species/Synchronize IDs' window)";
461    }
462    return newshort;
463}
464
465GB_ERROR AWTC_pars_names(GBDATA *gb_main, bool *isWarningPtr) {
466    // rename species according to name_server
467    // 'isWarning' is set to true, in case of duplicates-warning
468
469    arb_progress gen_progress("Generating new shortnames (IDs)");
470    GB_ERROR     err       = name_server.connect(gb_main);
471    bool         isWarning = false;
472
473    if (!err) {
474        err = GBT_begin_rename_session(gb_main, 1);
475        if (!err) {
476            char     *ali_name = GBT_get_default_alignment(gb_main);
477            GB_ERROR  warning  = NULp;
478
479            if (!ali_name) {
480                err = GB_await_error();
481            }
482            else {
483                long     spcount = GBT_get_species_count(gb_main);
484                GB_HASH *hash    = GBS_create_hash(spcount, GB_IGNORE_CASE);
485
486                if (spcount) {
487                    arb_progress  progress("Renaming species", spcount);
488                    const char   *add_field = AW_get_nameserver_addid(gb_main);
489
490                    for (GBDATA *gb_species = GBT_first_species(gb_main);
491                         gb_species && !err;
492                         gb_species = GBT_next_species(gb_species))
493                    {
494                        GBDATA *gb_name      = GB_entry(gb_species, "name");
495                        GBDATA *gb_full_name = GB_entry(gb_species, "full_name");
496                        GBDATA *gb_acc       = GBT_gen_accession_number(gb_species, ali_name);
497                        GBDATA *gb_addfield  = add_field[0] ? GB_entry(gb_species, add_field) : NULp;
498
499                        char *name      = gb_name      ? GB_read_string   (gb_name)      : strdup("");
500                        char *full_name = gb_full_name ? GB_read_string   (gb_full_name) : strdup("");
501                        char *acc       = gb_acc       ? GB_read_string   (gb_acc)       : strdup("");
502                        char *addid     = gb_addfield  ? GB_read_as_string(gb_addfield)  : strdup(""); // empty value will be set to default by nameserver
503
504                        char *shrt = NULp;
505
506                        if (full_name[0] || acc[0] || addid[0]) {
507                            if (aisc_nput(name_server.getLink(), AN_LOCAL, name_server.getLocs(),
508                                          LOCAL_FULL_NAME,  full_name,
509                                          LOCAL_ACCESSION,  acc,
510                                          LOCAL_ADDID,      addid,
511                                          LOCAL_ADVICE,     name,
512                                          NULp)) {
513                                err = "Connection Problems with the NAME_SERVER";
514                            }
515                            if (aisc_get(name_server.getLink(), AN_LOCAL, name_server.getLocs(),
516                                         LOCAL_GET_SHORT,   &shrt,
517                                         NULp)) {
518                                err = "Connection Problems with the NAME_SERVER";
519                            }
520                        }
521                        else {
522                            shrt = strdup(name);
523                        }
524                        if (!err) {
525                            char *newshrt = AWTC_create_numbered_suffix(hash, shrt, warning);
526                            if (newshrt) freeset(shrt, newshrt);
527
528                            GBS_incr_hash(hash, shrt);
529                            err = GBT_rename_species(name, shrt, true);
530                        }
531
532                        free(shrt);
533                        free(addid);
534                        free(acc);
535                        free(full_name);
536                        free(name);
537
538                        progress.inc_and_check_user_abort(err);
539                    }
540                }
541                else {
542                    gen_progress.sub_progress_skipped(); // trigger skipped subcounter
543                }
544                GBS_free_hash(hash);
545                free(ali_name);
546            }
547
548            if (err) GBT_abort_rename_session();
549            else err = GBT_commit_rename_session();
550
551            if (!err) {
552                err = warning;
553                if (warning) isWarning = true;
554            }
555        }
556        name_server.disconnect();
557    }
558
559    if (isWarningPtr) *isWarningPtr = isWarning;
560    gen_progress.done(); // needed if err
561
562    return err;
563}
564
565
566static void awt_rename_cb(AW_window *aww, GBDATA *gb_main) {
567    GB_ERROR error = AWTC_pars_names(gb_main, NULp);
568    if (error) aw_message(error);
569    aww->get_root()->awar(AWAR_TREE_REFRESH)->touch();
570}
571
572
573AW_window *AWTC_create_rename_window(AW_root *root, GBDATA *gb_main) {
574    AW_window_simple *aws = new AW_window_simple;
575    aws->init(root, "AUTORENAME_SPECIES", "Synchronize species IDs");
576
577    aws->load_xfig("awtc/autoren.fig");
578
579    aws->at("close");
580    aws->callback(AW_POPDOWN);
581    aws->create_button("CLOSE", "CLOSE", "C");
582
583    aws->at("help");
584    aws->callback(makeHelpCallback("rename.hlp"));
585    aws->create_button("HELP", "HELP", "H");
586
587    aws->at("config");
588    aws->callback(makeWindowCallback(AW_popup_namesadmin_window, gb_main));
589    aws->create_autosize_button("CONFIG_NAMESERVER", "Configure nameserver", "n");
590
591    aws->at("go");
592    aws->highlight();
593    aws->callback(makeWindowCallback(awt_rename_cb, gb_main));
594    aws->create_button("GO", "GO", "G");
595
596    return aws;
597}
598
599UniqueNameDetector::UniqueNameDetector(GBDATA *gb_item_data, long additionalEntries) {
600    hash = GBS_create_hash(GB_number_of_subentries(gb_item_data)+additionalEntries, GB_IGNORE_CASE);
601
602    for (GBDATA *gb_item = GB_child(gb_item_data); gb_item; gb_item = GB_nextChild(gb_item)) {
603        GBDATA *gb_name = GB_entry(gb_item, "name");
604        if (gb_name) { // item has name -> insert to hash
605            GBS_write_hash(hash, GB_read_char_pntr(gb_name), 1);
606        }
607    }
608}
609
610UniqueNameDetector::~UniqueNameDetector() { GBS_free_hash(hash); }
611
612static char *makeUniqueShortName(const char *prefix, UniqueNameDetector& existing) {
613    // generates a non-existing short-name (name starts with prefix)
614    //
615    // returns NULp if it fails
616
617    char *result     = NULp;
618    int   prefix_len = strlen(prefix);
619
620    aw_assert(prefix_len<8); // prefix has to be shorter than 8 chars!
621    if (prefix_len<8) {
622        const int max_nums[8] = { 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10 };
623        static int next_try[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
624
625        int  max_num = max_nums[prefix_len];
626        char short_name[9];
627        strcpy(short_name, prefix);
628
629        char *dig_pos = short_name+prefix_len;
630        int   num     = next_try[prefix_len];
631        int   stop    = num ? num-1 : max_num;
632
633        while (num != stop) {
634            sprintf(dig_pos, "%i", num);
635            ++num;
636            if (!existing.name_known(short_name)) {
637                result = strdup(short_name);
638                break;
639            }
640            if (num == max_num && stop != max_num) num = 0;
641        }
642        if (num == max_num) num = 0;
643        next_try[prefix_len] = num;
644    }
645    return result;
646}
647
648char *AWTC_makeUniqueShortName(const char *prefix, UniqueNameDetector& existingNames) {
649    // generates a unique species name from prefix
650    // (prefix will be fillup with zero digits and then shortened down to first char)
651    //
652    // returns NULp if failed (and exports error)
653
654    int  len = strlen(prefix);
655    char p[9];
656    strncpy(p, prefix, 8);
657
658    if (len>8) len = 8;
659    else {
660        if (len == 0) p[len++] = 'x'; // don't use digit as first character
661        while (len<8) p[len++] = '0';
662    }
663
664    p[len] = 0;
665
666    char *result = NULp;
667
668    for (int l = len-1; l>0 && !result; --l) {
669        p[l]   = 0;
670        result = makeUniqueShortName(p, existingNames);
671    }
672
673    aw_assert(!result || strlen(result) <= 8);
674    if (!result) GB_export_errorf("Failed to create unique species ID (prefix='%s')", prefix);
675
676    return result;
677}
678
679char *AWTC_generate_random_name(UniqueNameDetector& existingNames) {
680    char *new_species_name = NULp;
681    char  short_name[9];
682    int   count            = 10000;
683
684    short_name[8] = 0;
685    while (count--) {
686        short_name[0] = 'a'+GB_random(26); // first character has to be alpha
687
688        for (int x=1; x<8; ++x) {
689            int r = GB_random(36); // rest may be alphanumeric
690            short_name[x] = r<10 ? ('0'+r) : ('a'+r-10);
691        }
692
693        if (!existingNames.name_known(short_name)) {
694            new_species_name = strdup(short_name);
695            break;
696        }
697    }
698
699    if (!new_species_name) {
700        aw_message("Failed to generate a random name - retrying (this might hang forever)");
701        return AWTC_generate_random_name(existingNames);
702    }
703
704    return new_species_name;
705}
706
707int AWTC_name_quality(const char *short_name) {
708    // result 0 = ok for external tools
709    //        1 = ok for ARB
710    //        2 = not ok
711
712    int len         = -1;
713    int alnum_count = 0;
714    int ascii_count = 0;
715
716    while (char c = short_name[++len]) {
717        alnum_count += (isalnum(c) != 0);
718        ascii_count += (c > 32 && c < 127);
719    }
720
721    if (len>0) {
722        if (len <= 8) {
723            if (len == alnum_count) return 0; // ok for external programs
724        }
725        if (len == ascii_count) return 1; // ok for ARB
726    }
727    return 2; // not ok
728}
Note: See TracBrowser for help on using the repository browser.