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) { |
---|
52 | //! create a full path |
---|
53 | gb_getenv_hook oldHook = GB_install_getenv_hook(expand_symbolic_directories); |
---|
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, ":", true); |
---|
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]); |
---|
103 | GB_ERROR error = GB_create_directory(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 | |
---|
112 | enum DirSortOrder { |
---|
113 | SORT_ALPHA, |
---|
114 | SORT_DATE, |
---|
115 | SORT_SIZE, |
---|
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 | |
---|
203 | aw_assert(!GB_have_error()); |
---|
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, false); |
---|
227 | } |
---|
228 | |
---|
229 | void fill(); |
---|
230 | |
---|
231 | void filename_changed(bool post_filter_change_HACK); |
---|
232 | |
---|
233 | GB_ULONG get_newest_dir_modtime() const { |
---|
234 | ConstStrArray dirs; |
---|
235 | GBT_split_string(dirs, awr->awar(def_dir)->read_char_pntr(), ":", true); |
---|
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 = GBS_stropen(FILENAME_MAX+1); |
---|
270 | if (path) { |
---|
271 | GBS_strcat(out, path); |
---|
272 | GBS_chrcat(out, '/'); |
---|
273 | } |
---|
274 | |
---|
275 | if (fullname) GBS_strcat(out, fullname); |
---|
276 | |
---|
277 | if (GB_is_directory(GBS_mempntr(out))) { |
---|
278 | // if 'out' contains a directory now, 'name' was lacking a filename |
---|
279 | // (in this case it was only a directory) |
---|
280 | GBS_strcat(out, "/noname"); // invent a name |
---|
281 | } |
---|
282 | |
---|
283 | if (suffix) { |
---|
284 | GBS_chrcat(out, '.'); |
---|
285 | GBS_strcat(out, suffix); |
---|
286 | } |
---|
287 | |
---|
288 | free(path); |
---|
289 | free(fullname); |
---|
290 | |
---|
291 | return GBS_strclose(out); |
---|
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 | |
---|
439 | class DuplicateLinkFilter { |
---|
440 | set<string> insertedDirectories; |
---|
441 | |
---|
442 | public: |
---|
443 | DuplicateLinkFilter() {} |
---|
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 | |
---|
498 | void File_selection::fill() { |
---|
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, ":", true); |
---|
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 | |
---|
797 | static void fill_fileselection_cb(AW_root*, File_selection *cbs) { |
---|
798 | if (!avoid_multi_refresh) { |
---|
799 | LocallyModify<bool> flag(avoid_multi_refresh, true); |
---|
800 | cbs->fill(); |
---|
801 | } |
---|
802 | } |
---|
803 | static void fileselection_filename_changed_cb(AW_root*, File_selection *cbs) { |
---|
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 | } |
---|
813 | static void fileselection_filter_changed_cb(AW_root*, File_selection *cbs) { |
---|
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 | |
---|
832 | struct selbox_autorefresh_info { |
---|
833 | unsigned long modtime; |
---|
834 | File_selection *acbs; |
---|
835 | selbox_autorefresh_info *next; |
---|
836 | }; |
---|
837 | static selbox_autorefresh_info *autorefresh_info = NULp; |
---|
838 | |
---|
839 | static unsigned autorefresh_selboxes(AW_root *) { |
---|
840 | selbox_autorefresh_info *check = autorefresh_info; |
---|
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.. |
---|
852 | return SELBOX_AUTOREFRESH_FREQUENCY; |
---|
853 | } |
---|
854 | |
---|
855 | static void selbox_install_autorefresh(AW_root *aw_root, File_selection *acbs) { |
---|
856 | if (!autorefresh_info) { // when installing first selbox |
---|
857 | aw_root->add_timed_callback(SELBOX_AUTOREFRESH_FREQUENCY, makeTimedCallback(autorefresh_selboxes)); |
---|
858 | // @@@ replace timer callback with AW_add_inotification! |
---|
859 | } |
---|
860 | |
---|
861 | selbox_autorefresh_info *install = new selbox_autorefresh_info; |
---|
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) { |
---|
871 | /*! Create a file selection box, this box needs 3 AWARS: |
---|
872 | * |
---|
873 | * 1. "$awar_prefix/filter" |
---|
874 | * 2. "$awar_prefix/directory" |
---|
875 | * 3. "$awar_prefix/file_name" |
---|
876 | * |
---|
877 | * (Note: The function AW_create_fileselection_awars() can be used to create them) |
---|
878 | * |
---|
879 | * the "$awar_prefix/file_name" contains the full filename |
---|
880 | * Use AW_get_selected_fullname() to read it. |
---|
881 | * |
---|
882 | * The items are placed at |
---|
883 | * |
---|
884 | * 1. "$at_prefix""filter" |
---|
885 | * 2. "$at_prefix""box" |
---|
886 | * 3. "$at_prefix""file_name" |
---|
887 | * |
---|
888 | * if disp_dirs == ANY_DIR, then show directories and files |
---|
889 | * if disp_dirs == MULTI_DIRS, then only show files, but from multiple directories |
---|
890 | * |
---|
891 | * pwd is the name of a 'shell environment variable' which indicates the base directory |
---|
892 | * (e.g. 'PWD' or 'ARBHOME') |
---|
893 | */ |
---|
894 | |
---|
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 | |
---|
900 | fill_fileselection_cb(NULp, acbs); |
---|
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() { |
---|
965 | TEST_EXPECT_EQUAL(detectBrowserCommand("hello"), NULp); |
---|
966 | TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f"), NULp); |
---|
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?"); |
---|
970 | TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f??"), NULp); |
---|
971 | TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f???"), NULp); |
---|
972 | TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f?cmd"), NULp); |
---|
973 | } |
---|
974 | |
---|
975 | void TEST_path_unfolding() { |
---|
976 | const char *currDir = GB_getcwd(); |
---|
977 | { |
---|
978 | gb_getenv_hook old = GB_install_getenv_hook(expand_symbolic_directories); |
---|
979 | |
---|
980 | TEST_EXPECT_EQUAL(GB_getenv("PWD"), currDir); |
---|
981 | TEST_EXPECT_EQUAL(GB_getenv("ARBHOME"), GB_getenvARBHOME()); |
---|
982 | TEST_EXPECT_NULL(GB_getenv("ARB_NONEXISTING_ENVAR")); |
---|
983 | |
---|
984 | GB_install_getenv_hook(old); |
---|
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 | |
---|