ARB
AW_file.cxx
Go to the documentation of this file.
1 // ================================================================ //
2 // //
3 // File : AW_file.cxx //
4 // Purpose : //
5 // //
6 // Institute of Microbiology (Technical University Munich) //
7 // http://www.arb-home.de/ //
8 // //
9 // ================================================================ //
10 
11 #include "aw_file.hxx"
12 #include "aw_awar.hxx"
13 #include "aw_root.hxx"
14 #include "aw_select.hxx"
15 #include "aw_msg.hxx"
16 #include "aw_question.hxx"
17 
18 #include <arbdbt.h>
19 #include <arb_file.h>
20 #include <arb_strbuf.h>
21 #include <arb_misc.h>
22 #include <arb_str.h>
23 #include <arb_strarray.h>
24 
25 #include <sys/stat.h>
26 #include <dirent.h>
27 #include <set>
28 #include <string>
29 
30 using std::set;
31 using std::string;
32 
33 #if defined(DEBUG)
34 // #define TRACE_FILEBOX
35 #endif // DEBUG
36 
37 static GB_CSTR expand_symbolic_directories(const char *pwd_envar) {
38  GB_CSTR res;
39 
40  if (strcmp(pwd_envar, "PWD") == 0) {
41  res = GB_getcwd();
42  }
43  else {
44  res = NULp;
45  }
46 
47  return res;
48 }
49 
50 
51 char *AW_unfold_path(const char *pwd_envar, const char *path) {
54  char *result = nulldup(GB_unfold_path(pwd_envar, path));
55  GB_install_getenv_hook(oldHook);
56  return result;
57 }
58 
59 char *AW_extract_directory(const char *path) {
60  const char *lslash = strrchr(path, '/');
61  if (!lslash) return NULp;
62 
63  char *result = strdup(path);
64  result[lslash-path] = 0;
65 
66  return result;
67 }
68 
69 // -----------------------------
70 // file selection boxes
71 
72 void AW_create_fileselection_awars(AW_root *awr, const char *awar_base, const char *directories, const char *filter, const char *file_name) {
73  int base_len = strlen(awar_base);
74  bool has_slash = awar_base[base_len-1] == '/';
75  char *awar_name = new char[base_len+30]; // use private buffer, because caller will most likely use GBS_global_string for arguments
76  AW_default default_file = AW_ROOT_DEFAULT;
77 
78  sprintf(awar_name, "%s%s", awar_base, "/directory"+int(has_slash));
79  AW_awar *awar_dir = awr->awar_string(awar_name, directories, default_file);
80 
81  sprintf(awar_name, "%s%s", awar_base, "/filter" + int(has_slash));
82  AW_awar *awar_filter = awr->awar_string(awar_name, filter, default_file);
83 
84  sprintf(awar_name, "%s%s", awar_base, "/file_name"+int(has_slash));
85  AW_awar *awar_filename = awr->awar_string(awar_name, file_name, default_file);
86 
87 #if defined(ASSERTION_USED)
88  bool is_tmp_awar = strncmp(awar_base, "tmp/", 4) == 0 || strncmp(awar_base, "/tmp/", 5) == 0;
89  aw_assert(is_tmp_awar); // you need to use a temp awar for file selections
90 #endif
91 
92  awar_dir->write_string(directories);
93  awar_filter->write_string(filter);
94  awar_filename->write_string(file_name);
95 
96  // create all (default) directories
97  {
98  ConstStrArray dirs;
99  GBT_split_string(dirs, directories, ":", SPLIT_DROPEMPTY);
100  for (unsigned i = 0; i<dirs.size(); ++i) {
101  if (!GB_is_directory(dirs[i])) {
102  fprintf(stderr, "Creating directory '%s'\n", dirs[i]);
104  if (error) aw_message(GBS_global_string("Failed to create directory '%s' (Reason: %s)", dirs[i], error));
105  }
106  }
107  }
108 
109  delete [] awar_name;
110 }
111 
116 
117  DIR_SORT_ORDERS // order count
118 };
119 
120 class LimitedTime {
121  double max_duration;
122  time_t start;
123  mutable bool aborted;
124 
125 public:
126  LimitedTime(double max_duration_seconds) : max_duration(max_duration_seconds) { reset(); }
127  void reset() {
128  time(&start);
129  aborted = false;
130  }
131  double allowed_duration() const { return max_duration; }
132  bool finished_in_time() const { return !aborted; }
133  bool available() const {
134  if (!aborted) {
135  time_t now;
136  time(&now);
137  aborted = difftime(now, start) > max_duration;
138  }
139  return !aborted;
140  }
141  void increase() { max_duration *= 2.5; }
142 };
143 
144 class File_selection { // @@@ derive from AW_selection?
145  AW_root *awr;
146 
147  AW_selection_list *filelist;
148 
149  char *def_name;
150  char *def_dir;
151  char *def_filter;
152 
153  char *pwd;
154  char *pwdx; // additional directories
155 
156  DirDisplay dirdisp;
157 
158  bool leave_wildcards;
159  bool filled_by_wildcard; // last fill done with wildcard?
160 
161  bool show_subdirs; // show or hide subdirs
162  bool show_hidden; // show or hide files/directories starting with '.'
163 
164  DirSortOrder sort_order;
165 
166  LimitedTime searchTime;
167 
168  int shown_name_len;
169 
170  void bind_callbacks();
171  void execute_browser_command(const char *browser_command);
172  void fill_recursive(const char *fulldir, int skipleft, const char *mask, bool recurse, bool showdir);
173 
174  void format_columns();
175 
176 public:
177 
178  File_selection(AW_root *aw_root, const char *awar_prefix, const char *pwd_, DirDisplay disp_dirs, bool allow_wildcards)
179  : awr(aw_root),
180  filelist(NULp),
181  pwd(strdup(pwd_)),
182  pwdx(NULp),
183  dirdisp(disp_dirs),
184  leave_wildcards(allow_wildcards),
185  filled_by_wildcard(false),
186  show_subdirs(true),
187  show_hidden(false),
188  sort_order(SORT_ALPHA),
189  searchTime(1.3)
190  {
191  {
192  char *multiple_dirs_in_pwd = strchr(pwd, '^');
193  if (multiple_dirs_in_pwd) {
194  multiple_dirs_in_pwd[0] = 0;
195  pwdx = multiple_dirs_in_pwd+1;
196  }
197  }
198 
199  def_name = GBS_string_eval(awar_prefix, "*=*/file_name");
200  def_dir = GBS_string_eval(awar_prefix, "*=*/directory");
201  def_filter = GBS_string_eval(awar_prefix, "*=*/filter");
202 
204 
205  bind_callbacks();
206  }
207 
208  void create_gui_elements(AW_window *aws, const char *at_prefix) {
209  aw_assert(!filelist);
210 
211  char buffer[1024];
212  sprintf(buffer, "%sfilter", at_prefix);
213  if (aws->at_ifdef(buffer)) {
214  aws->at(buffer);
215  aws->create_input_field(def_filter, 5);
216  }
217 
218  sprintf(buffer, "%sfile_name", at_prefix);
219  if (aws->at_ifdef(buffer)) {
220  aws->at(buffer);
221  aws->create_input_field(def_name, 20);
222  }
223 
224  sprintf(buffer, "%sbox", at_prefix);
225  aws->at(buffer);
226  filelist = aws->create_selection_list(def_name);
227  }
228 
229  void fill();
230 
231  void filename_changed(bool post_filter_change_HACK);
232 
234  ConstStrArray dirs;
235  GBT_split_string(dirs, awr->awar(def_dir)->read_char_pntr(), ":", SPLIT_DROPEMPTY);
236  unsigned long maxtof = 0;
237  for (unsigned i = 0; i<dirs.size(); ++i) {
238  unsigned long tof = GB_time_of_file(dirs[i]);
239  if (tof>maxtof) maxtof = tof;
240  }
241  return maxtof;
242  }
243 
244  void trigger_refresh() { awr->awar(def_dir)->touch(); }
245 };
246 
247 static GB_CSTR get_suffix(GB_CSTR fullpath) { // returns pointer behind '.' of suffix (or NULp if no suffix found)
248  GB_CSTR dot = strrchr(fullpath, '.');
249  if (!dot) return NULp;
250 
251  GB_CSTR lslash = strrchr(fullpath, '/');
252  if (lslash && lslash>dot) return NULp; // no . behind last /
253  return dot+1;
254 }
255 
256 
257 static char *set_suffix(const char *name, const char *suffix) {
258  // returns "name.suffix" (name may contain path information)
259  // - eliminates multiple dots
260  // - sets name to 'noname' if no name part is given
261 
262  char *path, *fullname;
263  GB_split_full_path(name, &path, &fullname, NULp, NULp);
264 
265  // remove dots and spaces from suffix:
266  while (suffix[0] == '.' || suffix[0] == ' ') ++suffix;
267  if (!suffix[0]) suffix = NULp;
268 
269  GBS_strstruct out(FILENAME_MAX+1);
270  if (path) {
271  out.cat(path);
272  out.put('/');
273  }
274 
275  if (fullname) out.cat(fullname);
276 
277  if (GB_is_directory(out.get_data())) {
278  // if 'out' contains a directory now, 'name' was lacking a filename
279  // (in this case it was only a directory)
280  out.cat("/noname"); // invent a name
281  }
282 
283  if (suffix) {
284  out.put('.');
285  out.cat(suffix);
286  }
287 
288  free(path);
289  free(fullname);
290 
291  return out.release();
292 }
293 
294 
295 inline const char *valid_path(const char *path) { return path[0] ? path : "."; }
296 
297 inline bool AW_is_dir(const char *path) { return GB_is_directory(valid_path(path)); }
298 inline bool AW_is_file(const char *path) { return GB_is_regularfile(valid_path(path)); }
299 inline bool AW_is_link(const char *path) { return GB_is_link(valid_path(path)); }
300 
301 void File_selection::execute_browser_command(const char *browser_command) {
302  if (strcmp(browser_command, "sort") == 0) {
303  sort_order = DirSortOrder((sort_order+1)%DIR_SORT_ORDERS);
304  }
305  else if (strcmp(browser_command, "hide") == 0) {
306  show_subdirs = false;
307  }
308  else if (strcmp(browser_command, "show") == 0) {
309  show_subdirs = true;
310  }
311  else if (strcmp(browser_command, "dot") == 0) {
312  show_hidden = !show_hidden;
313  }
314  else if (strcmp(browser_command, "inctime") == 0) {
315  searchTime.increase();
316  }
317  else {
318  aw_message(GBS_global_string("Unknown browser command '%s'", browser_command));
319  }
320 }
321 
322 inline int entryType(const char *entry) {
323  const char *typechar = "DFL";
324  for (int i = 0; typechar[i]; ++i) {
325  if (entry[0] == typechar[i]) return i;
326  }
327  return -1;
328 }
329 
330 void File_selection::format_columns() {
331  const int FORMATTED_TYPES = 3;
332 
333  int maxlen[FORMATTED_TYPES] = { 17, 17, 17 };
334 
335  for (int pass = 1; pass<=2; ++pass) {
336  AW_selection_list_iterator entry(filelist);
337  while (entry) {
338  const char *disp = entry.get_displayed();
339  int type = entryType(disp);
340 
341  if (type>=0) {
342  const char *q1 = strchr(disp, '?');
343  if (q1) {
344  const char *q2 = strchr(q1+1, '?');
345  if (q2) {
346  int len = q2-q1-1;
347  if (pass == 1) {
348  if (maxlen[type]<len) maxlen[type] = len;
349  }
350  else {
351  GBS_strstruct buf(200);
352  buf.ncat(disp, q1-disp);
353  buf.ncat(q1+1, len);
354  buf.nput(' ', maxlen[type]-len);
355  buf.cat(q2+1);
356  entry.set_displayed(buf.get_data());
357  }
358  }
359  }
360  }
361  ++entry;
362  }
363  }
364 }
365 
366 void File_selection::fill_recursive(const char *fulldir, int skipleft, const char *mask, bool recurse, bool showdir) {
367  DIR *dirp = opendir(fulldir);
368 
369 #if defined(TRACE_FILEBOX)
370  printf("fill_fileselection_recursive for directory '%s'\n", fulldir);
371 #endif // TRACE_FILEBOX
372 
373  if (!dirp) {
374  filelist->insert(GBS_global_string("x Your directory path is invalid (%s)", fulldir), "?");
375  return;
376  }
377 
378  struct dirent *dp;
379  for (dp = readdir(dirp); dp; dp = readdir(dirp)) {
380  const char *entry = dp->d_name;
381  char *nontruepath = GBS_global_string_copy("%s/%s", fulldir, entry);
382  char *fullname;
383 
384  if (strlen(fulldir)) fullname = strdup(GB_concat_full_path(fulldir, entry));
385  else fullname = strdup(GB_canonical_path(entry));
386 
387  if (AW_is_dir(fullname)) {
388  if (!(entry[0] == '.' && (!show_hidden || entry[1] == 0 || (entry[1] == '.' && entry[2] == 0)))) { // skip "." and ".." and dotdirs if requested
389  if (showdir) {
390  filelist->insert(GBS_global_string("D ?%s? (%s)", entry, fullname), fullname); // '?' used in format_columns()
391  }
392  if (recurse && !AW_is_link(nontruepath)) { // don't follow links
393  if (searchTime.available()) {
394  fill_recursive(nontruepath, skipleft, mask, recurse, showdir);
395  }
396  }
397  }
398  }
399  else {
400  if (GBS_string_matches(entry, mask, GB_IGNORE_CASE)) { // entry matches mask
401  if ((entry[0] != '.' || show_hidden) && AW_is_file(fullname)) { // regular existing file
402  struct stat stt;
403 
404  stat(fullname, &stt);
405 
406  char atime[256];
407  struct tm *tms = localtime(&stt.st_mtime);
408  strftime(atime, 255, "%Y/%m/%d %k:%M", tms);
409 
410  char *size = strdup(GBS_readable_size(stt.st_size, "b"));
411  char typechar = AW_is_link(nontruepath) ? 'L' : 'F';
412 
413  const char *sel_entry = NULp;
414  switch (sort_order) {
415  case SORT_ALPHA:
416  sel_entry = GBS_global_string("%c ?%s? %7s %s", typechar, nontruepath+skipleft, size, atime); // '?' used in format_columns()
417  break;
418  case SORT_DATE:
419  sel_entry = GBS_global_string("%c %s %7s %s", typechar, atime, size, nontruepath+skipleft);
420  break;
421  case SORT_SIZE:
422  sel_entry = GBS_global_string("%c %7s %s %s", typechar, size, atime, nontruepath+skipleft);
423  break;
424  case DIR_SORT_ORDERS: break;
425  }
426 
427  filelist->insert(sel_entry, nontruepath);
428  free(size);
429  }
430  }
431  }
432  free(fullname);
433  free(nontruepath);
434  }
435 
436  closedir(dirp);
437 }
438 
440  set<string> insertedDirectories;
441 
442 public:
444 
445  bool not_seen_yet(const string& dir) const { return insertedDirectories.find(dir) == insertedDirectories.end(); }
446  void register_directory(const string& dir) {
447  insertedDirectories.insert(dir);
448  }
449 };
450 
451 
452 static void show_soft_link(AW_selection_list *filelist, const char *envar, DuplicateLinkFilter& unDup) {
453  // adds a soft link (e.g. ARBMACROHOME or ARB_WORKDIR) into file selection box
454  // if content of 'envar' matches 'cwd' nothing is inserted
455 
456  const char *expanded_dir = expand_symbolic_directories(envar);
457  if (!expanded_dir) expanded_dir = GB_getenv(envar);
458 
459  if (expanded_dir) {
460  string edir(expanded_dir);
461 
462  if (unDup.not_seen_yet(edir)) {
463  unDup.register_directory(edir);
464  const char *entry = GBS_global_string("$ %-18s(%s)", GBS_global_string("'%s'", envar), expanded_dir);
465  filelist->insert(entry, expanded_dir);
466  }
467  }
468 }
469 
470 inline bool fileOrLink(const char *d) { return d[0] == 'F' || d[0] == 'L'; }
471 inline const char *gotounit(const char *d) {
472  ++d;
473  while (d[0] == ' ') ++d;
474  while (d[0] != ' ') ++d;
475  while (d[0] == ' ') ++d;
476  return d;
477 }
478 static int cmpBySize(const char *disp1, const char *disp2) {
479  if (fileOrLink(disp1) && fileOrLink(disp2)) {
480  const char *u1 = gotounit(disp1);
481  const char *u2 = gotounit(disp2);
482 
483  if (u1[0] != u2[0]) { // diff units
484  static const char *units = "bkMGTPEZY"; // see also ../CORE/arb_misc.cxx@Tera
485 
486  const char *p1 = strchr(units, u1[0]);
487  const char *p2 = strchr(units, u2[0]);
488  if (p1 != p2) {
489  return p1-p2;
490  }
491  }
492  }
493  return ARB_stricmp(disp1, disp2);
494 }
495 
496 inline bool contains_wildcards(const char *name) { return strpbrk(name, "?*") != NULp; }
497 
499  AW_root *aw_root = awr;
500  filelist->clear();
501 
502  char *filter = aw_root->awar(def_filter)->read_string();
503  char *name = aw_root->awar(def_name)->read_string();
504 
505  const char *name_only = NULp;
506  {
507  char *slash = strrchr(name, '/');
508  name_only = slash ? slash+1 : name;
509  }
510 
511  StrArray dirs;
512  {
513  char *diru = aw_root->awar(def_dir)->read_string();
514  if (dirdisp == MULTI_DIRS) {
515  ConstStrArray cdirs;
516  GBT_split_string(cdirs, diru, ":", SPLIT_DROPEMPTY);
517  for (unsigned i = 0; i<cdirs.size(); ++i) dirs.put(strdup(cdirs[i]));
518  }
519  else {
520  if (name[0] == '/' && AW_is_dir(name)) {
521  dirs.put(strdup(name));
522  name_only = "";
523  }
524  else {
525  char *fulldir = AW_unfold_path(pwd, diru);
526  dirs.put(fulldir);
527  }
528  }
529  free(diru);
530  }
531 
532  filled_by_wildcard = contains_wildcards(name_only);
533 
534  if (dirdisp == ANY_DIR) {
535  aw_assert(dirs.size() == 1);
536  const char *fulldir = dirs[0];
537 
538  DuplicateLinkFilter unDup;
539  unDup.register_directory(fulldir);
540 
541  if (filled_by_wildcard) {
542  if (leave_wildcards) {
543  filelist->insert(GBS_global_string(" ALL '%s' in '%s'", name_only, fulldir), name);
544  }
545  else {
546  filelist->insert(GBS_global_string(" ALL '%s' in+below '%s'", name_only, fulldir), name);
547  }
548  }
549  else {
550  filelist->insert(GBS_global_string(" CONTENTS OF '%s'", fulldir), fulldir);
551  if (filter[0]) {
552  filelist->insert(GBS_global_string("! Find all (*%s)", filter), "*");
553  }
554  }
555 
556  if (strcmp("/", fulldir)) {
557  filelist->insert("! \'PARENT DIR\' (..)", "..");
558  }
559  if (show_subdirs) {
560  show_soft_link(filelist, pwd, unDup);
561 
562  if (pwdx) { // additional directories
563  char *start = pwdx;
564  while (start) {
565  char *multiple = strchr(start, '^');
566  if (multiple) {
567  multiple[0] = 0;
568  show_soft_link(filelist, start, unDup);
569  multiple[0] = '^';
570  start = multiple+1;
571  }
572  else {
573  show_soft_link(filelist, start, unDup);
574  start = NULp;
575  }
576  }
577  }
578 
579  show_soft_link(filelist, "HOME", unDup);
580  show_soft_link(filelist, "PWD", unDup);
581  show_soft_link(filelist, "ARB_WORKDIR", unDup);
582 
583  filelist->insert("! Sub-directories (shown)", GBS_global_string("%s?hide?", name));
584  }
585  else {
586  filelist->insert("! Sub-directories (hidden)", GBS_global_string("%s?show?", name));
587  }
588  filelist->insert(GBS_global_string("! Hidden (%s)", show_hidden ? "shown" : "not shown"), GBS_global_string("%s?dot?", name));
589  }
590  else {
591  aw_assert(dirdisp == MULTI_DIRS);
592  }
593 
594  static const char *order_name[DIR_SORT_ORDERS] = { "alpha", "date", "size" };
595 
596  filelist->insert(GBS_global_string("! Sort order (%s)", order_name[sort_order]), GBS_global_string("%s?sort?", name));
597 
598  bool insert_dirs = dirdisp == ANY_DIR && show_subdirs;
599 
600  searchTime.reset(); // limits time spent in fill_recursive
601  for (unsigned i = 0; i<dirs.size(); ++i) {
602  const char *fulldir = dirs[i];
603  if (filled_by_wildcard) {
604  if (leave_wildcards) {
605  fill_recursive(fulldir, strlen(fulldir)+1, name_only, false, insert_dirs);
606  }
607  else {
608  if (dirdisp == ANY_DIR) { // recursive wildcarded search
609  fill_recursive(fulldir, strlen(fulldir)+1, name_only, true, false);
610  }
611  else {
612  char *mask = GBS_global_string_copy("%s*%s", name_only, filter);
613  fill_recursive(fulldir, strlen(fulldir)+1, mask, false, false);
614  free(mask);
615  }
616  }
617  }
618  else {
619  char *mask = GBS_global_string_copy("*%s", filter);
620 
621  fill_recursive(fulldir, strlen(fulldir)+1, mask, false, insert_dirs);
622  free(mask);
623  }
624  }
625 
626  if (!searchTime.finished_in_time()) {
627  filelist->insert(GBS_global_string("! Find aborted (after %.1fs; click to search longer)", searchTime.allowed_duration()), GBS_global_string("%s?inctime?", name));
628  }
629 
630  if (sort_order == SORT_SIZE) {
631  filelist->sortCustom(cmpBySize);
632  }
633  else {
634  filelist->sort(false, false);
635  }
636  format_columns();
637  filelist->insert_default("", "");
638  filelist->update();
639 
640  if (filled_by_wildcard && !leave_wildcards) { // avoid returning wildcarded filename (if !leave_wildcards)
641  aw_root->awar(def_name)->write_string("");
642  }
643 
644  free(name);
645  free(filter);
646 }
647 
648 static const char *detectBrowserCommand(const char *fname) {
649  // if fname ends with '?word?'
650  // -> returns ptr to 'word'
651  // NULp otherwise
652 
653  const char *qm1 = NULp;
654  const char *qm2 = NULp;
655  const char *qm3 = ARB_strchrnul(fname, '?');
656 
657  while (qm3[0]) {
658  qm1 = qm2;
659  qm2 = qm3;
660  qm3 = ARB_strchrnul(qm2+1, '?');
661  }
662 
663  if (qm1 && qm1[0]) {
664  aw_assert(qm2 && qm2[0]);
665  if (!qm2[1]) { // 2nd ? at EOS
666  int cmdlen = qm2-qm1-1;
667  if (cmdlen>=1) return qm1+1;
668  }
669  }
670  return NULp;
671 }
672 
673 void File_selection::filename_changed(bool post_filter_change_HACK) {
674  AW_root *aw_root = awr;
675  const char *fname = aw_root->awar(def_name)->read_string();
676 
677 #if defined(TRACE_FILEBOX)
678  printf("fileselection_filename_changed_cb:\n"
679  "- fname ='%s'\n", fname);
680 #endif // TRACE_FILEBOX
681 
682  if (fname[0]) {
683  const char *browser_command = detectBrowserCommand(fname);
684  if (browser_command) {
685  char *browser_command_copy = ARB_strndup(browser_command, strlen(browser_command)-1);
686  char *fname_no_cmd = ARB_strpartdup(fname, browser_command-2);
687 
688  aw_root->awar(def_name)->write_string(fname_no_cmd); // re-write w/o browser_command
689  execute_browser_command(browser_command_copy);
690  trigger_refresh();
691 
692  free(fname_no_cmd);
693  free(browser_command_copy);
694  }
695  else if (dirdisp != MULTI_DIRS) {
696  char *newName = NULp;
697  char *dir = aw_root->awar(def_dir)->read_string();
698 
699 #if defined(TRACE_FILEBOX)
700  printf("- dir ='%s'\n", dir);
701 #endif // TRACE_FILEBOX
702 
703  if (fname[0] == '/' || fname[0] == '~') {
704  newName = strdup(GB_canonical_path(fname));
705  }
706  else {
707  if (dir[0]) {
708  if (dir[0] == '/') {
709  newName = strdup(GB_concat_full_path(dir, fname));
710  }
711  else {
712  char *fulldir = NULp;
713 
714  if (dir[0] == '.') fulldir = AW_unfold_path(pwd, dir);
715  else fulldir = strdup(dir);
716 
717  newName = strdup(GB_concat_full_path(fulldir, fname));
718  free(fulldir);
719  }
720  }
721  else {
722  newName = AW_unfold_path(pwd, fname);
723  }
724  }
725 
726  // Allow to select symbolic links for files (i.e. do not automatically switch to link-target;
727  // doing so made it impossible to load quicksaves done versus a master DB).
728  if (newName && strcmp(fname, newName) != 0) {
729  if (!GB_is_directory(fname) && !GB_is_directory(newName)) {
730  if (GB_is_link(fname) ) freenull(newName); // do not follow symlink!
731  }
732  }
733 
734  if (newName) {
735 #if defined(TRACE_FILEBOX)
736  printf("- newName ='%s'\n", newName);
737 #endif // TRACE_FILEBOX
738 
739  if (AW_is_dir(newName)) {
740  aw_root->awar(def_name)->write_string("");
741  aw_assert(dirdisp != MULTI_DIRS); // overwriting content unwanted if displaying MULTI_DIRS
742  aw_root->awar(def_dir)->write_string(newName);
743  aw_root->awar(def_name)->write_string("");
744  }
745  else {
746  char *lslash = strrchr(newName, '/');
747  if (lslash) {
748  if (lslash == newName) { // root directory
749  aw_assert(dirdisp != MULTI_DIRS); // overwriting content unwanted if displaying MULTI_DIRS
750  aw_root->awar(def_dir)->write_string("/"); // write directory part
751  }
752  else {
753  lslash[0] = 0;
754  aw_assert(dirdisp != MULTI_DIRS); // overwriting content unwanted if displaying MULTI_DIRS
755  aw_root->awar(def_dir)->write_string(newName); // write directory part
756  lslash[0] = '/';
757  }
758  }
759 
760  // now check the correct suffix :
761  {
762  char *filter = aw_root->awar(def_filter)->read_string();
763  if (filter[0]) {
764  char *pfilter = strrchr(filter, '.');
765  pfilter = pfilter ? pfilter+1 : filter;
766 
767  char *suffix = (char*)get_suffix(newName); // cast ok, since get_suffix points into newName
768 
769  if (!suffix || strcmp(suffix, pfilter) != 0) {
770  if (suffix && post_filter_change_HACK) {
771  if (suffix[-1] == '.') suffix[-1] = 0;
772  }
773  freeset(newName, set_suffix(newName, pfilter));
774  }
775  }
776  free(filter);
777  }
778 
779  if (strcmp(newName, fname) != 0) {
780  aw_root->awar(def_name)->write_string(newName); // loops back if changed !!!
781  }
782  }
783  }
784  free(dir);
785 
786  if (strchr(fname, '*')) { // wildcard -> search for suffix
787  trigger_refresh();
788  }
789 
790  free(newName);
791  }
792  }
793 }
794 
795 static bool avoid_multi_refresh = false;
796 
798  if (!avoid_multi_refresh) {
799  LocallyModify<bool> flag(avoid_multi_refresh, true);
800  cbs->fill();
801  }
802 }
804  if (!avoid_multi_refresh) {
805  LocallyModify<bool> flag(avoid_multi_refresh, true);
806  cbs->filename_changed(false);
807  cbs->fill();
808  }
809  else {
810  cbs->filename_changed(false);
811  }
812 }
814  if (!avoid_multi_refresh) {
815  LocallyModify<bool> flag(avoid_multi_refresh, true);
816  cbs->filename_changed(true);
817  cbs->fill();
818  }
819  else {
820  cbs->filename_changed(true);
821  }
822 }
823 
824 void File_selection::bind_callbacks() {
825  awr->awar(def_name) ->add_callback(makeRootCallback(fileselection_filename_changed_cb, this));
826  awr->awar(def_dir) ->add_callback(makeRootCallback(fill_fileselection_cb, this));
827  awr->awar(def_filter)->add_callback(makeRootCallback(fileselection_filter_changed_cb, this));
828 }
829 
830 #define SELBOX_AUTOREFRESH_FREQUENCY 3000 // refresh every XXX ms
831 
833  unsigned long modtime;
836 };
838 
839 static unsigned autorefresh_selboxes(AW_root *) {
841 
842  while (check) {
843  GB_ULONG mtime = check->acbs->get_newest_dir_modtime();
844  if (mtime != check->modtime) {
845  check->modtime = mtime;
846  check->acbs->trigger_refresh();
847  }
848  check = check->next;
849  }
850 
851  // refresh again and again and again..
853 }
854 
855 static void selbox_install_autorefresh(AW_root *aw_root, File_selection *acbs) {
856  if (!autorefresh_info) { // when installing first selbox
858  // @@@ replace timer callback with AW_add_inotification!
859  }
860 
862 
863  install->acbs = acbs;
864  install->modtime = acbs->get_newest_dir_modtime();
865 
866  install->next = autorefresh_info;
867  autorefresh_info = install;
868 }
869 
870 void AW_create_fileselection(AW_window *aws, const char *awar_prefix, const char *at_prefix, const char *pwd, DirDisplay disp_dirs, bool allow_wildcards) {
895  AW_root *aw_root = aws->get_root();
896  File_selection *acbs = new File_selection(aw_root, awar_prefix, pwd, disp_dirs, allow_wildcards);
897 
898  acbs->create_gui_elements(aws, at_prefix);
899 
901  fileselection_filename_changed_cb(NULp, acbs); // this fixes the path name
902 
903  selbox_install_autorefresh(aw_root, acbs);
904 }
905 
906 char *AW_get_selected_fullname(AW_root *awr, const char *awar_prefix) { // @@@ add flag to select whether wildcards are allowed
907  char *file = awr->awar(GBS_global_string("%s/file_name", awar_prefix))->read_string();
908  if (file[0] != '/') {
909  // if name w/o directory was entered by hand (or by default) then append the directory :
910 
911  char *awar_dir_name = GBS_global_string_copy("%s/directory", awar_prefix);
912  AW_awar *awar_dir = awr->awar_no_error(awar_dir_name);
913 
914  if (!awar_dir) {
915  // file selection box was not active (happens e.g. for print tree)
916  awar_dir = awr->awar_string(awar_dir_name, GB_getcwd());
917  }
918 
919  aw_assert(awar_dir);
920 
921  char *dir = awar_dir->read_string();
922  if (!dir[0]) { // empty -> fillin current dir
923  awar_dir->write_string(GB_getcwd());
924  freeset(dir, awar_dir->read_string());
925  }
926 
927  char *full = strdup(GB_concat_full_path(dir, file));
928 
929  free(dir);
930  free(file);
931 
932  file = full;
933 
934  free(awar_dir_name);
935  }
936 
937  return file;
938 }
939 
940 void AW_set_selected_fullname(AW_root *awr, const char *awar_prefix, const char *to_fullname) {
941  awr->awar(GBS_global_string("%s/file_name", awar_prefix))->write_string(to_fullname);
942 }
943 
944 void AW_refresh_fileselection(AW_root *awr, const char *awar_prefix) {
945  // call optionally to force instant refresh
946  // (automatic refresh is done every SELBOX_AUTOREFRESH_FREQUENCY)
947 
948  awr->awar(GBS_global_string("%s/directory", awar_prefix))->touch();
949 }
950 
951 // --------------------------------------------------------------------------------
952 
953 #ifdef UNIT_TESTS
954 #include <test_unit.h>
955 
956 #define TEST_EXPECT_EQUAL_DUPPED(cs1, cs2) \
957  do { \
958  char *s1, *s2; \
959  TEST_EXPECT_EQUAL(s1 = (cs1), s2 = (cs2)); \
960  free(s1); \
961  free(s2); \
962  } while(0)
963 
964 void TEST_detectBrowserCommand() {
967  TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f?cmd?"), "cmd?");
968  TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f?cmd?x"), NULp);
969  TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f?cmd?2nd?"), "2nd?");
973 }
974 
975 void TEST_path_unfolding() {
976  const char *currDir = GB_getcwd();
977  {
979 
980  TEST_EXPECT_EQUAL(GB_getenv("PWD"), currDir);
982  TEST_EXPECT_NULL(GB_getenv("ARB_NONEXISTING_ENVAR"));
983 
985  }
986 
987  TEST_EXPECT_EQUAL_DUPPED(AW_unfold_path("PWD", "/usr"), strdup("/usr"));
988  TEST_EXPECT_EQUAL_DUPPED(AW_unfold_path("PWD", "../tests"), strdup(GB_path_in_ARBHOME("UNIT_TESTER/tests")));
989  TEST_EXPECT_EQUAL_DUPPED(AW_unfold_path("ARB_NONEXISTING_ENVAR", "."), strdup(currDir));
990 }
991 TEST_PUBLISH(TEST_path_unfolding);
992 
993 #endif // UNIT_TESTS
994 
995 
double allowed_duration() const
Definition: AW_file.cxx:131
static int cmpBySize(const char *disp1, const char *disp2)
Definition: AW_file.cxx:478
string result
GB_TYPES type
static bool avoid_multi_refresh
Definition: AW_file.cxx:795
void sort(bool backward, bool case_sensitive)
Definition: AW_select.cxx:502
bool contains_wildcards(const char *name)
Definition: AW_file.cxx:496
static unsigned autorefresh_selboxes(AW_root *)
Definition: AW_file.cxx:839
size_t size() const
Definition: arb_strarray.h:85
selbox_autorefresh_info * next
Definition: AW_file.cxx:835
void AW_set_selected_fullname(AW_root *awr, const char *awar_prefix, const char *to_fullname)
Definition: AW_file.cxx:940
return string(buffer, length)
static void dot(double **i, double **j, double **k)
Definition: trnsprob.cxx:59
const char * valid_path(const char *path)
Definition: AW_file.cxx:295
void reset()
Definition: AW_file.cxx:127
void insert_default(const char *displayed, const AW_scalar &value)
Definition: AW_select.cxx:385
bool not_seen_yet(const string &dir) const
Definition: AW_file.cxx:445
GB_ULONG get_newest_dir_modtime() const
Definition: AW_file.cxx:233
void at(int x, int y)
Definition: AW_at.cxx:93
bool fileOrLink(const char *d)
Definition: AW_file.cxx:470
int ARB_stricmp(const char *s1, const char *s2)
Definition: arb_str.h:28
static void fileselection_filter_changed_cb(AW_root *, File_selection *cbs)
Definition: AW_file.cxx:813
static void selbox_install_autorefresh(AW_root *aw_root, File_selection *acbs)
Definition: AW_file.cxx:855
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:203
char * AW_get_selected_fullname(AW_root *awr, const char *awar_prefix)
Definition: AW_file.cxx:906
bool GB_have_error()
Definition: arb_msg.cxx:338
char * GBS_string_eval(const char *insource, const char *icommand)
Definition: admatch.cxx:699
char * release()
Definition: arb_strbuf.h:129
void cat(const char *from)
Definition: arb_strbuf.h:199
void add_timed_callback(int ms, const TimedCallback &tcb)
Definition: AW_root.cxx:538
char * ARB_strpartdup(const char *start, const char *end)
Definition: arb_string.h:51
unsigned long modtime
Definition: AW_file.cxx:833
static void show_soft_link(AW_selection_list *filelist, const char *envar, DuplicateLinkFilter &unDup)
Definition: AW_file.cxx:452
NOT4PERL gb_getenv_hook GB_install_getenv_hook(gb_getenv_hook hook)
Definition: adsocket.cxx:699
char buffer[MESSAGE_BUFFERSIZE]
Definition: seq_search.cxx:34
GB_CSTR GB_canonical_path(const char *anypath)
Definition: adsocket.cxx:1000
GB_CSTR GB_getenvARBHOME(void)
Definition: adsocket.cxx:579
unsigned long GB_ULONG
Definition: arbdb_base.h:42
AW_awar * add_callback(const RootCallback &cb)
Definition: AW_awar.cxx:231
static const char * detectBrowserCommand(const char *fname)
Definition: AW_file.cxx:648
void insert(const char *displayed, const AW_scalar &value)
Definition: AW_select.cxx:380
void register_directory(const string &dir)
Definition: AW_file.cxx:446
static HelixNrInfo * start
void filename_changed(bool post_filter_change_HACK)
Definition: AW_file.cxx:673
#define TEST_PUBLISH(testfunction)
Definition: test_unit.h:1517
const char * read_char_pntr() const
Definition: AW_awar.cxx:168
bool finished_in_time() const
Definition: AW_file.cxx:132
#define true
Definition: ureadseq.h:14
unsigned long GB_time_of_file(const char *path)
Definition: arb_file.cxx:44
#define aw_assert(bed)
Definition: aw_position.hxx:29
#define false
Definition: ureadseq.h:13
const char * GBS_readable_size(unsigned long long size, const char *unit_suffix)
Definition: arb_misc.cxx:23
void touch()
Definition: AW_awar.cxx:207
static selbox_autorefresh_info * autorefresh_info
Definition: AW_file.cxx:837
static void error(const char *msg)
Definition: mkptypes.cxx:96
void AW_create_fileselection_awars(AW_root *awr, const char *awar_base, const char *directories, const char *filter, const char *file_name)
Definition: AW_file.cxx:72
GB_CSTR GB_path_in_ARBHOME(const char *relative_path)
Definition: adsocket.cxx:1149
DirSortOrder
Definition: AW_file.cxx:112
const char *(* gb_getenv_hook)(const char *varname)
Definition: arbdb.h:138
bool AW_is_link(const char *path)
Definition: AW_file.cxx:299
char * AW_unfold_path(const char *pwd_envar, const char *path)
Definition: AW_file.cxx:51
static GB_CSTR expand_symbolic_directories(const char *pwd_envar)
Definition: AW_file.cxx:37
char * read_string() const
Definition: AW_awar.cxx:198
AW_awar * awar_no_error(const char *awar)
end timer stuff
Definition: AW_root.cxx:549
AW_awar * awar(const char *awar)
Definition: AW_root.cxx:554
GB_CSTR GB_getcwd(void)
Definition: adfile.cxx:24
int entryType(const char *entry)
Definition: AW_file.cxx:322
static void fileselection_filename_changed_cb(AW_root *, File_selection *cbs)
Definition: AW_file.cxx:803
bool AW_is_dir(const char *path)
Definition: AW_file.cxx:297
static char * set_suffix(const char *name, const char *suffix)
Definition: AW_file.cxx:257
void AW_refresh_fileselection(AW_root *awr, const char *awar_prefix)
Definition: AW_file.cxx:944
void trigger_refresh()
Definition: AW_file.cxx:244
File_selection(AW_root *aw_root, const char *awar_prefix, const char *pwd_, DirDisplay disp_dirs, bool allow_wildcards)
Definition: AW_file.cxx:178
void create_gui_elements(AW_window *aws, const char *at_prefix)
Definition: AW_file.cxx:208
long int flag
Definition: f2c.h:39
GB_CSTR GB_getenv(const char *env)
Definition: adsocket.cxx:709
bool AW_is_file(const char *path)
Definition: AW_file.cxx:298
GB_CSTR GB_concat_full_path(const char *anypath_left, const char *anypath_right)
Definition: adsocket.cxx:1103
void create_input_field(const char *awar_name, int columns=0)
Definition: AW_button.cxx:857
#define TEST_EXPECT_NULL(n)
Definition: test_unit.h:1322
char * ARB_strndup(const char *start, int len)
Definition: arb_string.h:83
void increase()
Definition: AW_file.cxx:141
bool GB_is_directory(const char *path)
Definition: arb_file.cxx:176
const char * name_only(const char *fullpath)
Definition: AWTI_import.cxx:46
static void fill_fileselection_cb(AW_root *, File_selection *cbs)
Definition: AW_file.cxx:797
AW_selection_list * create_selection_list(const char *awar_name, int columns, int rows)
Definition: AW_button.cxx:1215
const char * ARB_strchrnul(const char *str, int chr)
Definition: arb_string.h:96
bool at_ifdef(const char *id)
Definition: AW_at.cxx:134
void aw_message(const char *msg)
Definition: AW_status.cxx:1142
const char * gotounit(const char *d)
Definition: AW_file.cxx:471
AW_root * get_root()
Definition: aw_window.hxx:359
void GB_split_full_path(const char *fullpath, char **res_dir, char **res_fullname, char **res_name_only, char **res_suffix)
Definition: adsocket.cxx:1268
File_selection * acbs
Definition: AW_file.cxx:834
#define NULp
Definition: cxxforward.h:116
bool GB_is_regularfile(const char *path)
Definition: arb_file.cxx:76
void AW_create_fileselection(AW_window *aws, const char *awar_prefix, const char *at_prefix, const char *pwd, DirDisplay disp_dirs, bool allow_wildcards)
Definition: AW_file.cxx:870
GB_ERROR write_string(const char *aw_string)
void GBT_split_string(ConstStrArray &dest, const char *namelist, const char *separator, SplitMode mode)
Definition: arb_strarray.h:223
bool GB_is_link(const char *path)
Definition: arb_file.cxx:83
const char * get_data() const
Definition: arb_strbuf.h:120
bool available() const
Definition: AW_file.cxx:133
char * AW_extract_directory(const char *path)
Definition: AW_file.cxx:59
DirDisplay
Definition: aw_file.hxx:25
AW_awar * awar_string(const char *var_name, const char *default_value="", AW_default default_file=AW_ROOT_DEFAULT)
Definition: AW_root.cxx:570
static GB_CSTR get_suffix(GB_CSTR fullpath)
Definition: AW_file.cxx:247
bool GBS_string_matches(const char *str, const char *expr, GB_CASE case_sens)
Definition: admatch.cxx:193
GB_CSTR GB_unfold_path(const char *pwd_envar, const char *path)
Definition: adsocket.cxx:1132
#define SELBOX_AUTOREFRESH_FREQUENCY
Definition: AW_file.cxx:830
#define AW_ROOT_DEFAULT
Definition: aw_base.hxx:106
#define TEST_EXPECT_EQUAL(expr, want)
Definition: test_unit.h:1294
LimitedTime(double max_duration_seconds)
Definition: AW_file.cxx:126
GB_ERROR GB_create_directory(const char *path)
char * GBS_global_string_copy(const char *templat,...)
Definition: arb_msg.cxx:194
void put(char c)
Definition: arb_strbuf.h:174
void sortCustom(sellist_cmp_fun cmp)
Definition: AW_select.cxx:478