1 /* vifm
2 * Copyright (C) 2001 Ken Steen.
3 * Copyright (C) 2011 xaizek.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 */
19
20 #include "filelist.h"
21
22 #ifdef _WIN32
23 #include <windows.h>
24 #include <fcntl.h>
25 #include <lm.h>
26 #include <winioctl.h>
27 #endif
28
29 #include <curses.h>
30
31 #include <sys/stat.h> /* stat */
32
33 #include <assert.h> /* assert() */
34 #include <errno.h> /* errno */
35 #include <limits.h> /* INT_MIN */
36 #include <stddef.h> /* NULL size_t */
37 #include <stdint.h> /* intptr_t uint64_t */
38 #include <stdio.h> /* snprintf() */
39 #include <stdlib.h> /* calloc() free() */
40 #include <string.h> /* memcmp() memcpy() memset() strcat() strcmp() strcpy()
41 strdup() strlen() */
42
43 #include "cfg/config.h"
44 #include "compat/fs_limits.h"
45 #include "compat/os.h"
46 #include "engine/autocmds.h"
47 #include "engine/mode.h"
48 #include "int/fuse.h"
49 #include "modes/dialogs/msg_dialog.h"
50 #include "modes/modes.h"
51 #include "modes/view.h"
52 #include "ui/cancellation.h"
53 #include "ui/column_view.h"
54 #include "ui/fileview.h"
55 #include "ui/statusbar.h"
56 #include "ui/statusline.h"
57 #include "ui/tabs.h"
58 #include "ui/ui.h"
59 #include "utils/dynarray.h"
60 #include "utils/env.h"
61 #include "utils/fs.h"
62 #include "utils/fsdata.h"
63 #include "utils/fswatch.h"
64 #include "utils/log.h"
65 #include "utils/macros.h"
66 #include "utils/matcher.h"
67 #include "utils/path.h"
68 #include "utils/regexp.h"
69 #include "utils/str.h"
70 #include "utils/string_array.h"
71 #include "utils/test_helpers.h"
72 #include "utils/trie.h"
73 #include "utils/utf8.h"
74 #include "utils/utils.h"
75 #include "filtering.h"
76 #include "flist_hist.h"
77 #include "flist_pos.h"
78 #include "flist_sel.h"
79 #include "fops_misc.h"
80 #include "macros.h"
81 #include "marks.h"
82 #include "opt_handlers.h"
83 #include "registers.h"
84 #include "running.h"
85 #include "sort.h"
86 #include "status.h"
87 #include "types.h"
88
89 static void init_flist(view_t *view);
90 static void reset_view(view_t *view);
91 static void init_view_history(view_t *view);
92 static int navigate_to_file_in_custom_view(view_t *view, const char dir[],
93 const char file[]);
94 static int fill_dir_entry_by_path(dir_entry_t *entry, const char path[]);
95 #ifndef _WIN32
96 static int fill_dir_entry(dir_entry_t *entry, const char path[],
97 const struct dirent *d);
98 static int data_is_dir_entry(const struct dirent *d, const char path[]);
99 #else
100 static int fill_dir_entry(dir_entry_t *entry, const char path[],
101 const WIN32_FIND_DATAW *ffd);
102 static int data_is_dir_entry(const WIN32_FIND_DATAW *ffd, const char path[]);
103 #endif
104 static int flist_custom_finish_internal(view_t *view, CVType type, int reload,
105 const char dir[], int allow_empty);
106 static void on_location_change(view_t *view, int force);
107 static void disable_view_sorting(view_t *view);
108 static void enable_view_sorting(view_t *view);
109 static void exclude_in_compare(view_t *view, int selection_only);
110 static void mark_group(view_t *view, view_t *other, int idx);
111 static int got_excluded(view_t *view, const dir_entry_t *entry, void *arg);
112 static int exclude_temporary_entries(view_t *view);
113 static int is_temporary(view_t *view, const dir_entry_t *entry, void *arg);
114 static uint64_t recalc_entry_size(const dir_entry_t *entry, uint64_t old_size);
115 static uint64_t entry_calc_nitems(const dir_entry_t *entry);
116 static void load_dir_list_internal(view_t *view, int reload, int draw_only);
117 static int populate_dir_list_internal(view_t *view, int reload);
118 static int populate_custom_view(view_t *view, int reload);
119 static int entry_exists(view_t *view, const dir_entry_t *entry, void *arg);
120 static void zap_compare_view(view_t *view, view_t *other, zap_filter filter,
121 void *arg);
122 static int find_separator(view_t *view, int idx);
123 static void update_dir_watcher(view_t *view);
124 static int custom_list_is_incomplete(const view_t *view);
125 static int is_dead_or_filtered(view_t *view, const dir_entry_t *entry,
126 void *arg);
127 static void update_entries_data(view_t *view);
128 static int is_dir_big(const char path[]);
129 static void free_view_entries(view_t *view);
130 static int update_dir_list(view_t *view, int reload);
131 static void start_dir_list_change(view_t *view, dir_entry_t **entries, int *len,
132 int reload);
133 static void finish_dir_list_change(view_t *view, dir_entry_t *entries, int len);
134 static int add_file_entry_to_view(const char name[], const void *data,
135 void *param);
136 static void sort_dir_list(int msg, view_t *view);
137 static void merge_lists(view_t *view, dir_entry_t *entries, int len);
138 TSTATIC void check_file_uniqueness(view_t *view);
139 static void add_to_trie(trie_t *trie, view_t *view, dir_entry_t *entry);
140 static int is_in_trie(trie_t *trie, view_t *view, dir_entry_t *entry,
141 void **data);
142 static void merge_entries(dir_entry_t *new, const dir_entry_t *prev);
143 static int correct_pos(view_t *view, int pos, int dist, int closest);
144 static int rescue_from_empty_filelist(view_t *view);
145 static void add_parent_entry(view_t *view, dir_entry_t **entries, int *count);
146 static void init_dir_entry(view_t *view, dir_entry_t *entry, const char name[]);
147 static dir_entry_t * alloc_dir_entry(dir_entry_t **list, int list_size);
148 static int tree_has_changed(const dir_entry_t *entries, size_t nchildren);
149 static void find_dir_in_cdpath(const char base_dir[], const char dst[],
150 char buf[], size_t buf_size);
151 static entries_t list_sibling_dirs(view_t *view);
152 static entries_t flist_list_in(view_t *view, const char path[], int only_dirs,
153 int can_include_parent);
154 static dir_entry_t * pick_sibling(view_t *view, entries_t parent_dirs,
155 int offset, int wrap, int *wrapped);
156 static int iter_entries(view_t *view, dir_entry_t **entry,
157 entry_predicate pred);
158 static int mark_selected(view_t *view);
159 static int set_position_by_path(view_t *view, const char path[]);
160 static int flist_load_tree_internal(view_t *view, const char path[],
161 int reload);
162 static int make_tree(view_t *view, const char path[], int reload,
163 trie_t *excluded_paths);
164 static void tree_from_cv(view_t *view);
165 static int complete_tree(const char name[], int valid, const void *parent_data,
166 void *data, void *arg);
167 static void reset_entry_list(view_t *view, dir_entry_t **entries, int *count);
168 static void drop_tops(view_t *view, dir_entry_t *entries, int *nentries,
169 int extra);
170 static int add_files_recursively(view_t *view, const char path[],
171 trie_t *excluded_paths, int parent_pos, int no_direct_parent);
172 static int file_is_visible(view_t *view, const char name[], int is_dir,
173 const void *data, int apply_local_filter);
174 static int add_directory_leaf(view_t *view, const char path[], int parent_pos);
175 static int init_parent_entry(view_t *view, dir_entry_t *entry,
176 const char path[]);
177
178 void
init_filelists(void)179 init_filelists(void)
180 {
181 fview_setup();
182
183 flist_init_view(&rwin);
184 flist_init_view(&lwin);
185
186 curr_view = &lwin;
187 other_view = &rwin;
188 }
189
190 void
flist_init_view(view_t * view)191 flist_init_view(view_t *view)
192 {
193 init_flist(view);
194 fview_init(view);
195
196 reset_view(view);
197
198 init_view_history(view);
199 fview_sorting_updated(view);
200 }
201
202 /* Initializes file list part of the view. */
203 static void
init_flist(view_t * view)204 init_flist(view_t *view)
205 {
206 view->list_pos = 0;
207 view->last_seen_pos = -1;
208 view->history_num = 0;
209 view->history_pos = 0;
210 view->on_slow_fs = 0;
211 view->has_dups = 0;
212
213 view->watched_dir = NULL;
214 view->last_dir = NULL;
215
216 view->matches = 0;
217
218 view->custom.entries = NULL;
219 view->custom.entry_count = 0;
220 view->custom.orig_dir = NULL;
221 view->custom.title = NULL;
222
223 /* Load fake empty element to make dir_entry valid. */
224 view->dir_entry = dynarray_extend(NULL, sizeof(dir_entry_t));
225 memset(view->dir_entry, 0, sizeof(*view->dir_entry));
226 view->dir_entry[0].name = strdup("");
227 view->dir_entry[0].type = FT_DIR;
228 view->dir_entry[0].hi_num = -1;
229 view->dir_entry[0].name_dec_num = -1;
230 view->dir_entry[0].origin = &view->curr_dir[0];
231 view->list_rows = 1;
232 }
233
234 void
flist_free_view(view_t * view)235 flist_free_view(view_t *view)
236 {
237 /* For the application, we don't need to zero out fields after freeing them,
238 * but doing so allows reusing this function in tests. */
239
240 int i;
241
242 for(i = 0; i < view->list_rows; ++i)
243 {
244 fentry_free(view, &view->dir_entry[i]);
245 }
246 dynarray_free(view->dir_entry);
247 view->dir_entry = NULL;
248 view->list_rows = 0;
249
250 for(i = 0; i < view->custom.entry_count; ++i)
251 {
252 fentry_free(view, &view->custom.entries[i]);
253 }
254 dynarray_free(view->custom.entries);
255 view->custom.entries = NULL;
256 view->custom.entry_count = 0;
257
258 update_string(&view->custom.next_title, NULL);
259 update_string(&view->custom.orig_dir, NULL);
260 update_string(&view->custom.title, NULL);
261 trie_free(view->custom.excluded_paths);
262 trie_free(view->custom.paths_cache);
263 view->custom.excluded_paths = NULL;
264 view->custom.paths_cache = NULL;
265
266 for(i = 0; i < view->local_filter.entry_count; ++i)
267 {
268 fentry_free(view, &view->local_filter.entries[i]);
269 }
270 dynarray_free(view->local_filter.entries);
271 view->local_filter.entries = NULL;
272 view->local_filter.entry_count = 0;
273
274 /* Two pointer fields below don't contain valid data that needs to be freed,
275 * zeroing them for tests and to at least mention them to signal that they
276 * weren't forgotten. */
277 view->local_filter.unfiltered = NULL;
278 view->local_filter.saved = NULL;
279 view->local_filter.unfiltered_count = 0;
280
281 update_string(&view->local_filter.prev, NULL);
282 free(view->local_filter.poshist);
283 view->local_filter.poshist = NULL;
284
285 filter_dispose(&view->local_filter.filter);
286 filter_dispose(&view->auto_filter);
287 matcher_free(view->manual_filter);
288 view->manual_filter = NULL;
289 update_string(&view->prev_manual_filter, NULL);
290 update_string(&view->prev_auto_filter, NULL);
291
292 view->custom.type = CV_REGULAR;
293
294 fswatch_free(view->watch);
295 view->watch = NULL;
296 update_string(&view->watched_dir, NULL);
297
298 update_string(&view->last_dir, NULL);
299
300 flist_free_cache(view, &view->left_column);
301 flist_free_cache(view, &view->right_column);
302
303 update_string(&view->last_curr_file, NULL);
304
305 free_string_array(view->saved_selection, view->nsaved_selection);
306 view->nsaved_selection = 0;
307 view->saved_selection = NULL;
308
309 flist_hist_resize(view, 0);
310
311 cs_reset(&view->cs);
312
313 columns_free(view->columns);
314 view->columns = NULL;
315
316 update_string(&view->view_columns, NULL);
317 update_string(&view->view_columns_g, NULL);
318 update_string(&view->sort_groups, NULL);
319 update_string(&view->sort_groups_g, NULL);
320 update_string(&view->preview_prg, NULL);
321 update_string(&view->preview_prg_g, NULL);
322
323 modview_info_free(view->vi);
324 view->vi = NULL;
325
326 regfree(&view->primary_group);
327
328 marks_clear_view(view);
329 }
330
331 void
reset_views(void)332 reset_views(void)
333 {
334 reset_view(&lwin);
335 reset_view(&rwin);
336 }
337
338 /* Loads some of view parameters that should be restored on configuration
339 * reloading (e.g. on :restart command). */
340 static void
reset_view(view_t * view)341 reset_view(view_t *view)
342 {
343 fview_reset(view);
344 filters_view_reset(view);
345
346 view->hide_dot_g = view->hide_dot = 1;
347
348 memset(&view->sort[0], SK_NONE, sizeof(view->sort));
349 ui_view_sort_list_ensure_well_formed(view, view->sort);
350 memcpy(&view->sort_g[0], &view->sort[0], sizeof(view->sort_g));
351 }
352
353 /* Allocates memory for view history smartly (handles huge values). */
354 static void
init_view_history(view_t * view)355 init_view_history(view_t *view)
356 {
357 if(cfg.history_len == 0)
358 return;
359
360 view->history = calloc(cfg.history_len, sizeof(history_t));
361 while(view->history == NULL)
362 {
363 cfg.history_len /= 2;
364 view->history = calloc(cfg.history_len, sizeof(history_t));
365 }
366 }
367
368 void
load_initial_directory(view_t * view,const char dir[])369 load_initial_directory(view_t *view, const char dir[])
370 {
371 /* Use current working directory as original location for custom views loaded
372 * from command-line via "-". */
373 if(view->curr_dir[0] == '\0' || strcmp(view->curr_dir, "-") == 0)
374 {
375 copy_str(view->curr_dir, sizeof(view->curr_dir), dir);
376 }
377 else
378 {
379 dir = view->curr_dir;
380 }
381
382 if(!is_root_dir(view->curr_dir))
383 {
384 chosp(view->curr_dir);
385 }
386 if(change_directory(view, dir) < 0)
387 {
388 leave_invalid_dir(view);
389 (void)change_directory(view, view->curr_dir);
390 }
391 }
392
393 dir_entry_t *
get_current_entry(const view_t * view)394 get_current_entry(const view_t *view)
395 {
396 if(view->list_pos < 0 || view->list_pos >= view->list_rows)
397 {
398 return NULL;
399 }
400
401 return &view->dir_entry[view->list_pos];
402 }
403
404 char *
get_current_file_name(view_t * view)405 get_current_file_name(view_t *view)
406 {
407 if(view->list_pos == -1)
408 {
409 static char empty_string[1];
410 return empty_string;
411 }
412 return get_current_entry(view)->name;
413 }
414
415 void
invert_sorting_order(view_t * view)416 invert_sorting_order(view_t *view)
417 {
418 if(memcmp(&view->sort_g[0], &view->sort[0], sizeof(view->sort_g)) == 0)
419 {
420 view->sort_g[0] = -view->sort_g[0];
421 }
422 view->sort[0] = -view->sort[0];
423 }
424
425 void
leave_invalid_dir(view_t * view)426 leave_invalid_dir(view_t *view)
427 {
428 struct stat s;
429 char *const path = view->curr_dir;
430
431 if(fuse_try_updir_from_a_mount(path, view))
432 {
433 return;
434 }
435
436 /* Use stat() directly to skip all possible optimizations. It might be a bit
437 * heavier, but this is quite rare error-recovery procedure, we can afford it
438 * here. Even in the worst case of going to the root, this isn't that much
439 * calls. */
440 while((os_stat(path, &s) != 0 || !(s.st_mode & S_IFDIR) ||
441 !directory_accessible(path)) && is_path_well_formed(path))
442 {
443 if(fuse_try_updir_from_a_mount(path, view))
444 {
445 break;
446 }
447
448 remove_last_path_component(path);
449 }
450
451 ensure_path_well_formed(path);
452 }
453
454 void
navigate_to(view_t * view,const char path[])455 navigate_to(view_t *view, const char path[])
456 {
457 if(change_directory(view, path) >= 0)
458 {
459 load_dir_list(view, 0);
460 fview_cursor_redraw(view);
461 }
462 }
463
464 void
navigate_back(view_t * view)465 navigate_back(view_t *view)
466 {
467 const char *const dest = flist_custom_active(view)
468 ? view->custom.orig_dir
469 : view->last_dir;
470 if(dest != NULL)
471 {
472 navigate_to(view, dest);
473 }
474 }
475
476 void
navigate_to_file(view_t * view,const char dir[],const char file[],int preserve_cv)477 navigate_to_file(view_t *view, const char dir[], const char file[],
478 int preserve_cv)
479 {
480 if(flist_custom_active(view) && preserve_cv)
481 {
482 /* Try to find requested element in custom list of files. */
483 if(navigate_to_file_in_custom_view(view, dir, file) == 0)
484 {
485 return;
486 }
487 }
488
489 /* Do not change directory if we already there. */
490 if(!paths_are_equal(view->curr_dir, dir))
491 {
492 if(change_directory(view, dir) >= 0)
493 {
494 load_dir_list(view, 0);
495 }
496 }
497
498 if(paths_are_equal(view->curr_dir, dir))
499 {
500 (void)fpos_ensure_selected(view, file);
501 }
502 }
503
504 /* navigate_to_file() helper that tries to find requested file in custom view.
505 * Returns non-zero on failure to find such file, otherwise zero is returned. */
506 static int
navigate_to_file_in_custom_view(view_t * view,const char dir[],const char file[])507 navigate_to_file_in_custom_view(view_t *view, const char dir[],
508 const char file[])
509 {
510 char full_path[PATH_MAX + 1];
511
512 snprintf(full_path, sizeof(full_path), "%s/%s", dir, file);
513
514 if(custom_list_is_incomplete(view))
515 {
516 const dir_entry_t *entry;
517 entry = entry_from_path(view, view->local_filter.entries,
518 view->local_filter.entry_count, full_path);
519 if(entry == NULL)
520 {
521 /* No such entry in the view at all. */
522 return 1;
523 }
524
525 if(!local_filter_matches(view, entry))
526 {
527 /* The item is filtered-out by current settings, undo this filtering. */
528 local_filter_remove(view);
529 load_dir_list(view, 1);
530 }
531 }
532
533 if(set_position_by_path(view, full_path) != 0)
534 {
535 /* File might not exist anymore at that location. */
536 return 1;
537 }
538
539 ui_view_schedule_redraw(view);
540 return 0;
541 }
542
543 int
change_directory(view_t * view,const char directory[])544 change_directory(view_t *view, const char directory[])
545 {
546 /* TODO: refactor this big function change_directory(). */
547
548 char dir_dup[PATH_MAX + 1];
549 const int was_in_custom_view = flist_custom_active(view);
550 int location_changed;
551
552 if(is_dir_list_loaded(view))
553 {
554 flist_hist_save(view);
555 }
556
557 if(is_path_absolute(directory))
558 {
559 canonicalize_path(directory, dir_dup, sizeof(dir_dup));
560 }
561 else
562 {
563 char newdir[PATH_MAX + 1];
564 const char *const dir = flist_get_dir(view);
565 #ifdef _WIN32
566 if(directory[0] == '/')
567 {
568 snprintf(newdir, sizeof(newdir), "%c:%s", dir[0], directory);
569 }
570 else
571 #endif
572 {
573 snprintf(newdir, sizeof(newdir), "%s/%s", dir, directory);
574 }
575 canonicalize_path(newdir, dir_dup, sizeof(dir_dup));
576 }
577
578 system_to_internal_slashes(dir_dup);
579
580 if(cfg.chase_links)
581 {
582 char real_path[PATH_MAX + 1];
583 if(os_realpath(dir_dup, real_path) == real_path)
584 {
585 /* Do this on success only, if realpath() fails, just go with the original
586 * path. */
587 canonicalize_path(real_path, dir_dup, sizeof(dir_dup));
588 system_to_internal_slashes(dir_dup);
589 }
590 }
591
592 if(!is_root_dir(dir_dup))
593 chosp(dir_dup);
594
595 if(!is_valid_dir(dir_dup))
596 {
597 show_error_msgf("Directory Access Error", "Cannot open %s", dir_dup);
598 copy_str(view->curr_dir, sizeof(view->curr_dir), dir_dup);
599 leave_invalid_dir(view);
600 copy_str(dir_dup, sizeof(dir_dup), view->curr_dir);
601 }
602
603 location_changed = (stroscmp(dir_dup, flist_get_dir(view)) != 0);
604
605 /* Check if we're exiting from a FUSE mounted top level directory. */
606 if(is_parent_dir(directory) && fuse_is_mount_point(view->curr_dir))
607 {
608 /* No other pane in any tab should be inside subtree that might be
609 * unmounted. */
610 if(tabs_visitor_count(view->curr_dir) == 1)
611 {
612 const int r = fuse_try_unmount(view);
613 if(r != 0)
614 {
615 return r;
616 }
617 }
618 else if(fuse_try_updir_from_a_mount(view->curr_dir, view))
619 {
620 /* On success fuse_try_updir_from_a_mount() calls change_directory()
621 * recursively to change directory, so we can just leave. */
622 return 1;
623 }
624 }
625
626 /* Clean up any excess separators */
627 if(!is_root_dir(view->curr_dir))
628 chosp(view->curr_dir);
629 if(view->last_dir != NULL && !is_root_dir(view->last_dir))
630 chosp(view->last_dir);
631
632 #ifndef _WIN32
633 if(!path_exists(dir_dup, DEREF))
634 #else
635 if(!is_valid_dir(dir_dup))
636 #endif
637 {
638 LOG_SERROR_MSG(errno, "Can't access \"%s\"", dir_dup);
639 log_cwd();
640
641 show_error_msgf("Directory Access Error", "Cannot open %s", dir_dup);
642
643 flist_sel_stash(view);
644 return -1;
645 }
646
647 if(os_access(dir_dup, X_OK) != 0 && !is_unc_root(dir_dup))
648 {
649 LOG_SERROR_MSG(errno, "Can't access(, X_OK) \"%s\"", dir_dup);
650 log_cwd();
651
652 show_error_msgf("Directory Access Error",
653 "You do not have execute access on %s", dir_dup);
654
655 flist_sel_stash(view);
656 return -1;
657 }
658
659 if(os_access(dir_dup, R_OK) != 0 && !is_unc_root(dir_dup))
660 {
661 LOG_SERROR_MSG(errno, "Can't access(, R_OK) \"%s\"", dir_dup);
662 log_cwd();
663
664 if(location_changed)
665 {
666 show_error_msgf("Directory Access Error",
667 "You do not have read access on %s", dir_dup);
668 }
669 }
670
671 if(vifm_chdir(dir_dup) != 0 && !is_unc_root(dir_dup))
672 {
673 LOG_SERROR_MSG(errno, "Can't chdir() \"%s\"", dir_dup);
674 log_cwd();
675
676 show_error_msgf("Change Directory Error", "Couldn't open %s", dir_dup);
677 return -1;
678 }
679
680 if(!is_root_dir(dir_dup))
681 {
682 chosp(dir_dup);
683 }
684
685 flist_sel_view_reloaded(view, location_changed);
686
687 /* Need to use dir_dup instead of calling get_cwd() to avoid resolving
688 * symbolic links in path. */
689 env_set("PWD", dir_dup);
690
691 if(location_changed)
692 {
693 replace_string(&view->last_dir, flist_get_dir(view));
694 view->on_slow_fs = is_on_slow_fs(dir_dup, cfg.slow_fs_list);
695 }
696
697 copy_str(view->curr_dir, sizeof(view->curr_dir), dir_dup);
698
699 if(is_dir_list_loaded(view))
700 {
701 flist_hist_setup(view, NULL, "", -1, -1);
702 }
703
704 /* Perform additional actions on leaving custom view. */
705 if(was_in_custom_view)
706 {
707 if(ui_view_unsorted(view))
708 {
709 enable_view_sorting(view);
710 }
711 if(cv_compare(view->custom.type))
712 {
713 view_t *const other = (view == curr_view) ? other_view : curr_view;
714
715 /* Indicate that this is not a compare view anymore. */
716 view->custom.type = CV_REGULAR;
717
718 /* Leave compare mode in both views at the same time. */
719 if(other->custom.type == CV_DIFF)
720 {
721 rn_leave(other, 1);
722 }
723 }
724 }
725
726 if(location_changed || was_in_custom_view)
727 {
728 on_location_change(view, location_changed);
729 }
730 if(location_changed)
731 {
732 view->location_changed = 1;
733 }
734 return 0;
735 }
736
737 int
is_dir_list_loaded(view_t * view)738 is_dir_list_loaded(view_t *view)
739 {
740 dir_entry_t *const entry = (view->list_rows < 1) ? NULL : &view->dir_entry[0];
741 return entry != NULL && entry->name[0] != '\0';
742 }
743
744 #ifdef _WIN32
745 /* Appends list of shares to filelist of the view. Returns zero on success,
746 * otherwise non-zero is returned. */
747 static int
fill_with_shared(view_t * view)748 fill_with_shared(view_t *view)
749 {
750 NET_API_STATUS res;
751 wchar_t *wserver;
752
753 wserver = to_wide(view->curr_dir + 2);
754 if(wserver == NULL)
755 {
756 show_error_msg("Memory Error", "Unable to allocate enough memory");
757 return 1;
758 }
759
760 do
761 {
762 PSHARE_INFO_0 buf_ptr;
763 DWORD er = 0, tr = 0, resume = 0;
764
765 res = NetShareEnum(wserver, 0, (LPBYTE *)&buf_ptr, -1, &er, &tr, &resume);
766 if(res == ERROR_SUCCESS || res == ERROR_MORE_DATA)
767 {
768 PSHARE_INFO_0 p;
769 DWORD i;
770
771 for(i = 0, p = buf_ptr; i < er; ++i, ++p)
772 {
773 dir_entry_t *dir_entry;
774 char *utf8_name;
775
776 dir_entry = alloc_dir_entry(&view->dir_entry, view->list_rows);
777 if(dir_entry == NULL)
778 {
779 show_error_msg("Memory Error", "Unable to allocate enough memory");
780 continue;
781 }
782
783 utf8_name = utf8_from_utf16((wchar_t *)p->shi0_netname);
784
785 init_dir_entry(view, dir_entry, utf8_name);
786 dir_entry->type = FT_DIR;
787
788 free(utf8_name);
789
790 ++view->list_rows;
791 }
792 NetApiBufferFree(buf_ptr);
793 }
794 }
795 while(res == ERROR_MORE_DATA);
796
797 free(wserver);
798
799 return (res != ERROR_SUCCESS);
800 }
801 #endif
802
803 char *
get_typed_entry_fpath(const dir_entry_t * entry)804 get_typed_entry_fpath(const dir_entry_t *entry)
805 {
806 const char *type_suffix;
807 char full_path[PATH_MAX + 1];
808
809 type_suffix = fentry_is_dir(entry) ? "/" : "";
810
811 get_full_path_of(entry, sizeof(full_path), full_path);
812 return format_str("%s%s", full_path, type_suffix);
813 }
814
815 int
flist_custom_active(const view_t * view)816 flist_custom_active(const view_t *view)
817 {
818 /* First check isn't enough on startup, which leads to false positives. Yet
819 * this implicit condition seems to be preferable to omit introducing function
820 * that would terminate custom view mode. */
821 return view->curr_dir[0] == '\0' && !is_null_or_empty(view->custom.orig_dir);
822 }
823
824 void
flist_custom_start(view_t * view,const char title[])825 flist_custom_start(view_t *view, const char title[])
826 {
827 free_dir_entries(view, &view->custom.entries, &view->custom.entry_count);
828 (void)replace_string(&view->custom.next_title, title);
829
830 trie_free(view->custom.paths_cache);
831 view->custom.paths_cache = trie_create();
832 }
833
834 dir_entry_t *
flist_custom_add(view_t * view,const char path[])835 flist_custom_add(view_t *view, const char path[])
836 {
837 char canonic_path[PATH_MAX + 1];
838 to_canonic_path(path, flist_get_dir(view), canonic_path,
839 sizeof(canonic_path));
840
841 /* Don't add duplicates. */
842 if(trie_put(view->custom.paths_cache, canonic_path) != 0)
843 {
844 return NULL;
845 }
846
847 return entry_list_add(view, &view->custom.entries, &view->custom.entry_count,
848 canonic_path);
849 }
850
851 dir_entry_t *
flist_custom_put(view_t * view,dir_entry_t * entry)852 flist_custom_put(view_t *view, dir_entry_t *entry)
853 {
854 char full_path[PATH_MAX + 1];
855 dir_entry_t *dir_entry;
856 size_t list_size = view->custom.entry_count;
857
858 get_full_path_of(entry, sizeof(full_path), full_path);
859
860 /* Don't add duplicates. */
861 if(trie_put(view->custom.paths_cache, full_path) != 0)
862 {
863 return NULL;
864 }
865
866 dir_entry = add_dir_entry(&view->custom.entries, &list_size, entry);
867 view->custom.entry_count = list_size;
868 return dir_entry;
869 }
870
871 void
flist_custom_add_separator(view_t * view,int id)872 flist_custom_add_separator(view_t *view, int id)
873 {
874 dir_entry_t *const dir_entry = alloc_dir_entry(&view->custom.entries,
875 view->custom.entry_count);
876 if(dir_entry != NULL)
877 {
878 init_dir_entry(view, dir_entry, "");
879 dir_entry->origin = strdup(flist_get_dir(view));
880 dir_entry->id = id;
881 ++view->custom.entry_count;
882 }
883 }
884
885 #ifndef _WIN32
886
887 /* Fills directory entry with information about file specified by the path.
888 * Returns non-zero on error, otherwise zero is returned. */
889 static int
fill_dir_entry_by_path(dir_entry_t * entry,const char path[])890 fill_dir_entry_by_path(dir_entry_t *entry, const char path[])
891 {
892 return fill_dir_entry(entry, path, NULL);
893 }
894
895 /* Fills fields of the entry from stat information of the file specified by its
896 * path. d is optional source of file type. Returns zero on success, otherwise
897 * non-zero is returned. */
898 static int
fill_dir_entry(dir_entry_t * entry,const char path[],const struct dirent * d)899 fill_dir_entry(dir_entry_t *entry, const char path[], const struct dirent *d)
900 {
901 struct stat s;
902
903 /* Load the inode information or leave blank values in the entry. */
904 if(os_lstat(path, &s) != 0)
905 {
906 LOG_SERROR_MSG(errno, "Can't lstat() \"%s\"", path);
907 return 1;
908 }
909
910 entry->type = get_type_from_mode(s.st_mode);
911 if(entry->type == FT_UNK)
912 {
913 entry->type = (d == NULL) ? FT_UNK : type_from_dir_entry(d, path);
914 }
915 if(entry->type == FT_UNK)
916 {
917 LOG_ERROR_MSG("Can't determine type of \"%s\"", path);
918 return 1;
919 }
920
921 entry->size = (uintmax_t)s.st_size;
922 entry->uid = s.st_uid;
923 entry->gid = s.st_gid;
924 entry->mode = s.st_mode;
925 entry->inode = s.st_ino;
926 entry->mtime = s.st_mtime;
927 entry->atime = s.st_atime;
928 entry->ctime = s.st_ctime;
929 entry->nlinks = s.st_nlink;
930
931 if(entry->type == FT_LINK)
932 {
933 struct stat s;
934
935 const SymLinkType symlink_type = get_symlink_type(path);
936 entry->dir_link = (symlink_type != SLT_UNKNOWN);
937
938 /* Query mode of symbolic link target. */
939 if(symlink_type != SLT_SLOW && os_stat(entry->name, &s) == 0)
940 {
941 entry->mode = s.st_mode;
942 }
943 }
944
945 return 0;
946 }
947
948 /* Checks whether file is a directory. Returns non-zero if so, otherwise zero
949 * is returned. */
950 static int
data_is_dir_entry(const struct dirent * d,const char path[])951 data_is_dir_entry(const struct dirent *d, const char path[])
952 {
953 return is_dirent_targets_dir(path, d);
954 }
955
956 #else
957
958 /* Fills directory entry with information about file specified by the path.
959 * Returns non-zero on error, otherwise zero is returned. */
960 static int
fill_dir_entry_by_path(dir_entry_t * entry,const char path[])961 fill_dir_entry_by_path(dir_entry_t *entry, const char path[])
962 {
963 wchar_t *utf16_path;
964 HANDLE hfind;
965 WIN32_FIND_DATAW ffd;
966
967 utf16_path = utf8_to_utf16(path);
968 hfind = FindFirstFileW(utf16_path, &ffd);
969 free(utf16_path);
970
971 if(hfind == INVALID_HANDLE_VALUE)
972 {
973 return 1;
974 }
975
976 fill_dir_entry(entry, path, &ffd);
977
978 FindClose(hfind);
979
980 return 0;
981 }
982
983 /* Fills fields of the entry from *ffd fields for the file specified by its
984 * path. type_hint is additional source of file type. Returns zero on success,
985 * Returns zero on success, otherwise non-zero is returned. */
986 static int
fill_dir_entry(dir_entry_t * entry,const char path[],const WIN32_FIND_DATAW * ffd)987 fill_dir_entry(dir_entry_t *entry, const char path[],
988 const WIN32_FIND_DATAW *ffd)
989 {
990 entry->size = (ffd->nFileSizeHigh*((uint64_t)MAXDWORD + 1))
991 + ffd->nFileSizeLow;
992 entry->attrs = ffd->dwFileAttributes;
993 entry->mtime = win_to_unix_time(ffd->ftLastWriteTime);
994 entry->atime = win_to_unix_time(ffd->ftLastAccessTime);
995 entry->ctime = win_to_unix_time(ffd->ftCreationTime);
996
997 if(is_win_symlink(ffd->dwFileAttributes, ffd->dwReserved0))
998 {
999 const SymLinkType symlink_type = get_symlink_type(path);
1000 entry->dir_link = (symlink_type != SLT_UNKNOWN);
1001
1002 entry->type = FT_LINK;
1003 }
1004 else if(ffd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1005 {
1006 /* Windows doesn't like returning size of directories when it can. */
1007 entry->size = get_file_size(entry->name);
1008 entry->type = FT_DIR;
1009 }
1010 else if(is_win_executable(path))
1011 {
1012 entry->type = FT_EXEC;
1013 }
1014 else
1015 {
1016 entry->type = FT_REG;
1017 }
1018
1019 return 0;
1020 }
1021
1022 /* Checks whether file is a directory. Returns non-zero if so, otherwise zero
1023 * is returned. */
1024 static int
data_is_dir_entry(const WIN32_FIND_DATAW * ffd,const char path[])1025 data_is_dir_entry(const WIN32_FIND_DATAW *ffd, const char path[])
1026 {
1027 return (ffd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
1028 }
1029
1030 #endif
1031
1032 int
flist_custom_finish(view_t * view,CVType type,int allow_empty)1033 flist_custom_finish(view_t *view, CVType type, int allow_empty)
1034 {
1035 return flist_custom_finish_internal(view, type, 0, flist_get_dir(view),
1036 allow_empty);
1037 }
1038
1039 /* Finishes file list population, handles empty resulting list corner case.
1040 * reload flag suppresses actions taken on location change. dir is current
1041 * directory of the view. Returns zero on success, otherwise (on empty list)
1042 * non-zero is returned. */
1043 static int
flist_custom_finish_internal(view_t * view,CVType type,int reload,const char dir[],int allow_empty)1044 flist_custom_finish_internal(view_t *view, CVType type, int reload,
1045 const char dir[], int allow_empty)
1046 {
1047 enum { NORMAL, CUSTOM, UNSORTED } previous;
1048 const int empty_view = (view->custom.entry_count == 0);
1049
1050 trie_free(view->custom.paths_cache);
1051 view->custom.paths_cache = NULL;
1052
1053 if(empty_view && !allow_empty)
1054 {
1055 free_dir_entries(view, &view->custom.entries, &view->custom.entry_count);
1056 update_string(&view->custom.next_title, NULL);
1057 return 1;
1058 }
1059
1060 free(view->custom.title);
1061 view->custom.title = view->custom.next_title;
1062 view->custom.next_title = NULL;
1063
1064 /* If there are no files and we are allowed to add ".." directory, do it. */
1065 if(empty_view || (!cv_unsorted(type) && cfg_parent_dir_is_visible(0)))
1066 {
1067 dir_entry_t *const dir_entry = alloc_dir_entry(&view->custom.entries,
1068 view->custom.entry_count);
1069 if(dir_entry != NULL)
1070 {
1071 init_dir_entry(view, dir_entry, "..");
1072 dir_entry->type = FT_DIR;
1073 dir_entry->origin = strdup(dir);
1074 ++view->custom.entry_count;
1075 }
1076 }
1077
1078 previous = (view->curr_dir[0] != '\0')
1079 ? NORMAL
1080 : (ui_view_unsorted(view) ? UNSORTED : CUSTOM);
1081
1082 if(previous == NORMAL)
1083 {
1084 /* Save current location in the directory before replacing it with custom
1085 * view. */
1086 if(is_dir_list_loaded(view))
1087 {
1088 flist_hist_save(view);
1089 }
1090
1091 (void)replace_string(&view->custom.orig_dir, dir);
1092 view->curr_dir[0] = '\0';
1093 }
1094
1095 /* Replace view file list with custom list. */
1096 free_dir_entries(view, &view->dir_entry, &view->list_rows);
1097 view->dir_entry = view->custom.entries;
1098 view->list_rows = view->custom.entry_count;
1099 view->custom.entries = NULL;
1100 view->custom.entry_count = 0;
1101 view->dir_entry = dynarray_shrink(view->dir_entry);
1102 view->filtered = 0;
1103 view->matches = 0;
1104
1105 /* Kind of custom view must be set to correct value before option loading and
1106 * sorting. */
1107 view->custom.type = type;
1108
1109 if(cv_unsorted(type))
1110 {
1111 /* Disabling sorting twice in a row erases sorting completely. */
1112 if(previous != UNSORTED)
1113 {
1114 disable_view_sorting(view);
1115 }
1116 }
1117 else if(previous == UNSORTED)
1118 {
1119 enable_view_sorting(view);
1120 }
1121
1122 if(!reload)
1123 {
1124 on_location_change(view, 0);
1125 if(cfg.cvoptions & CVO_AUTOCMDS)
1126 {
1127 vle_aucmd_execute("DirEnter", flist_get_dir(view), view);
1128 }
1129 }
1130
1131 sort_dir_list(0, view);
1132
1133 ui_view_schedule_redraw(view);
1134 fpos_ensure_valid_pos(view);
1135
1136 return 0;
1137 }
1138
1139 /* Perform actions on view location change. Force activates all actions
1140 * unconditionally otherwise they are checked against cvoptions. */
1141 static void
on_location_change(view_t * view,int force)1142 on_location_change(view_t *view, int force)
1143 {
1144 view->has_dups = 0;
1145
1146 free_dir_entries(view, &view->local_filter.entries,
1147 &view->local_filter.entry_count);
1148
1149 if(force || (cfg.cvoptions & CVO_LOCALFILTER))
1150 {
1151 filters_dir_updated(view);
1152 }
1153 /* Stage check is to skip body of the if in tests. */
1154 if(curr_stats.load_stage > 0 && (force || (cfg.cvoptions & CVO_LOCALOPTS)))
1155 {
1156 reset_local_options(view);
1157 }
1158 }
1159
1160 /* Disables view sorting saving its state for the future. */
1161 static void
disable_view_sorting(view_t * view)1162 disable_view_sorting(view_t *view)
1163 {
1164 memcpy(&view->custom.sort[0], &view->sort[0], sizeof(view->custom.sort));
1165 memset(&view->sort[0], SK_NONE, sizeof(view->sort));
1166 load_sort_option(view);
1167 }
1168
1169 /* Undoes was was done by disable_view_sorting(). */
1170 static void
enable_view_sorting(view_t * view)1171 enable_view_sorting(view_t *view)
1172 {
1173 memcpy(&view->sort[0], &view->custom.sort[0], sizeof(view->sort));
1174 load_sort_option(view);
1175 }
1176
1177 void
flist_custom_exclude(view_t * view,int selection_only)1178 flist_custom_exclude(view_t *view, int selection_only)
1179 {
1180 flist_set_marking(view, 0);
1181
1182 if(!flist_custom_active(view))
1183 {
1184 return;
1185 }
1186
1187 if(cv_compare(view->custom.type))
1188 {
1189 exclude_in_compare(view, selection_only);
1190 return;
1191 }
1192
1193 dir_entry_t *entry = NULL;
1194 trie_t *excluded = trie_create();
1195 while(iter_marked_entries(view, &entry))
1196 {
1197 char full_path[PATH_MAX + 1];
1198
1199 entry->temporary = 1;
1200
1201 get_full_path_of(entry, sizeof(full_path), full_path);
1202 (void)trie_put(excluded, full_path);
1203
1204 if(cv_tree(view->custom.type))
1205 {
1206 (void)trie_put(view->custom.excluded_paths, full_path);
1207 }
1208 }
1209
1210 /* Update copy of list of entries made by local filter (it might be empty,
1211 * which is OK). */
1212 (void)zap_entries(view, view->local_filter.entries,
1213 &view->local_filter.entry_count, &got_excluded, excluded, 1, 1);
1214 trie_free(excluded);
1215
1216 (void)exclude_temporary_entries(view);
1217 }
1218
1219 /* Removes marked files from compare view. Zero selection_only enables
1220 * excluding files that share ids with selected items. */
1221 static void
exclude_in_compare(view_t * view,int selection_only)1222 exclude_in_compare(view_t *view, int selection_only)
1223 {
1224 view_t *const other = (view == curr_view) ? other_view : curr_view;
1225 const int double_compare = (view->custom.type == CV_DIFF);
1226 const int n = other->list_rows;
1227 dir_entry_t *entry = NULL;
1228 while(iter_marked_entries(view, &entry))
1229 {
1230 if(selection_only)
1231 {
1232 entry->temporary = 1;
1233 if(double_compare)
1234 {
1235 other->dir_entry[entry - view->dir_entry].temporary = 1;
1236 }
1237 }
1238 else
1239 {
1240 mark_group(view, other, entry - view->dir_entry);
1241 }
1242 }
1243
1244 (void)exclude_temporary_entries(view);
1245 if(double_compare && exclude_temporary_entries(other) == n)
1246 {
1247 /* Leave compare mode if we excluded all files. */
1248 rn_leave(view, 1);
1249 }
1250 }
1251
1252 /* Selects all neighbours of the idx-th element that share its id. */
1253 static void
mark_group(view_t * view,view_t * other,int idx)1254 mark_group(view_t *view, view_t *other, int idx)
1255 {
1256 int i;
1257 int id;
1258
1259 if(view->dir_entry[idx].temporary)
1260 {
1261 return;
1262 }
1263
1264 id = view->dir_entry[idx].id;
1265
1266 for(i = idx - 1; i >= 0 && view->dir_entry[i].id == id; --i)
1267 {
1268 view->dir_entry[i].temporary = 1;
1269 if(view->custom.type == CV_DIFF)
1270 {
1271 other->dir_entry[i].temporary = 1;
1272 }
1273 }
1274 for(i = idx; i < view->list_rows && view->dir_entry[i].id == id; ++i)
1275 {
1276 view->dir_entry[i].temporary = 1;
1277 if(view->custom.type == CV_DIFF)
1278 {
1279 other->dir_entry[i].temporary = 1;
1280 }
1281 }
1282 }
1283
1284 /* zap_entries() filter to filter-out files, which were just excluded from the
1285 * view. Returns non-zero if entry is to be kept and zero otherwise. */
1286 static int
got_excluded(view_t * view,const dir_entry_t * entry,void * arg)1287 got_excluded(view_t *view, const dir_entry_t *entry, void *arg)
1288 {
1289 void *data;
1290 trie_t *const excluded = arg;
1291 int excluded_entry;
1292
1293 char full_path[PATH_MAX + 1];
1294 get_full_path_of(entry, sizeof(full_path), full_path);
1295
1296 excluded_entry = (trie_get(excluded, full_path, &data) == 0);
1297 return !excluded_entry;
1298 }
1299
1300 /* Excludes view entries that are marked as "temporary". Returns number of
1301 * items that were visible before. */
1302 static int
exclude_temporary_entries(view_t * view)1303 exclude_temporary_entries(view_t *view)
1304 {
1305 const int n = zap_entries(view, view->dir_entry, &view->list_rows,
1306 &is_temporary, NULL, 0, 1);
1307 (void)zap_entries(view, view->custom.entries, &view->custom.entry_count,
1308 &is_temporary, NULL, 1, 1);
1309
1310 fpos_ensure_valid_pos(view);
1311 ui_view_schedule_redraw(view);
1312
1313 return n;
1314 }
1315
1316 /* zap_entries() filter to filter-out files, which were marked for removal.
1317 * Returns non-zero if entry is to be kept and zero otherwise.*/
1318 static int
is_temporary(view_t * view,const dir_entry_t * entry,void * arg)1319 is_temporary(view_t *view, const dir_entry_t *entry, void *arg)
1320 {
1321 return !entry->temporary;
1322 }
1323
1324 void
flist_custom_clone(view_t * to,const view_t * from,int as_tree)1325 flist_custom_clone(view_t *to, const view_t *from, int as_tree)
1326 {
1327 dir_entry_t *dst, *src;
1328 int nentries;
1329 int i, j;
1330 const int from_tree = cv_tree(from->custom.type);
1331
1332 assert(flist_custom_active(from) && to->custom.paths_cache == NULL &&
1333 "Wrong state of destination view.");
1334
1335 replace_string(&to->custom.orig_dir, from->custom.orig_dir);
1336 to->curr_dir[0] = '\0';
1337
1338 replace_string(&to->custom.title,
1339 (from_tree && !as_tree) ? "from tree" : from->custom.title);
1340 to->custom.type = (ui_view_unsorted(from) || from_tree)
1341 ? (as_tree ? CV_CUSTOM_TREE : CV_VERY)
1342 : CV_REGULAR;
1343
1344 if(custom_list_is_incomplete(from))
1345 {
1346 src = from->local_filter.entries;
1347 nentries = from->local_filter.entry_count;
1348 }
1349 else
1350 {
1351 src = from->dir_entry;
1352 nentries = from->list_rows;
1353 }
1354
1355 dst = dynarray_extend(NULL, nentries*sizeof(*dst));
1356
1357 j = 0;
1358 for(i = 0; i < nentries; ++i)
1359 {
1360 if(!cv_tree(to->custom.type) && src[i].child_pos != 0 &&
1361 is_parent_dir(src[i].name))
1362 {
1363 continue;
1364 }
1365
1366 dst[j] = src[i];
1367 dst[j].name = strdup(dst[j].name);
1368 if(dst[j].origin == from->curr_dir)
1369 {
1370 dst[j].origin = to->curr_dir;
1371 }
1372 else
1373 {
1374 dst[j].origin = strdup(dst[j].origin);
1375 }
1376
1377 if(!as_tree)
1378 {
1379 /* As destination pane won't be a tree, erase tree-specific data, because
1380 * some tree-specific code is driven directly by these fields. */
1381 dst[j].child_count = 0;
1382 dst[j].child_pos = 0;
1383 }
1384
1385 ++j;
1386 }
1387
1388 free_dir_entries(to, &to->custom.entries, &to->custom.entry_count);
1389 free_dir_entries(to, &to->dir_entry, &to->list_rows);
1390 to->dir_entry = dst;
1391 to->list_rows = j;
1392
1393 to->filtered = 0;
1394
1395 if(ui_view_unsorted(to))
1396 {
1397 disable_view_sorting(to);
1398 }
1399 }
1400
1401 void
flist_custom_uncompress_tree(view_t * view)1402 flist_custom_uncompress_tree(view_t *view)
1403 {
1404 unsigned int i;
1405
1406 assert(cv_tree(view->custom.type) && "This function is for tree-view only!");
1407
1408 dir_entry_t *entries = view->dir_entry;
1409 size_t nentries = view->list_rows;
1410 int restore_parent = 0;
1411
1412 fsdata_t *const tree = fsdata_create(0, 0);
1413
1414 view->dir_entry = NULL;
1415 view->list_rows = 0;
1416
1417 for(i = 0U; i < nentries; ++i)
1418 {
1419 char full_path[PATH_MAX + 1];
1420 void *data = &entries[i];
1421
1422 if(entries[i].child_pos == 0 && is_parent_dir(entries[i].name))
1423 {
1424 fentry_free(view, &entries[i]);
1425 restore_parent = 1;
1426 continue;
1427 }
1428
1429 get_full_path_of(&entries[i], sizeof(full_path), full_path);
1430 fsdata_set(tree, full_path, &data, sizeof(data));
1431 }
1432
1433 if(fsdata_traverse(tree, &complete_tree, view) != 0)
1434 {
1435 reset_entry_list(view, &view->dir_entry, &view->list_rows);
1436 restore_parent = 0;
1437 }
1438
1439 fsdata_free(tree);
1440 dynarray_free(entries);
1441
1442 drop_tops(view, view->dir_entry, &view->list_rows, 0);
1443
1444 if(restore_parent)
1445 {
1446 add_parent_dir(view);
1447 }
1448 }
1449
1450 const char *
flist_get_dir(const view_t * view)1451 flist_get_dir(const view_t *view)
1452 {
1453 if(flist_custom_active(view))
1454 {
1455 assert(view->custom.orig_dir != NULL && "Wrong view dir state.");
1456 return view->custom.orig_dir;
1457 }
1458
1459 return view->curr_dir;
1460 }
1461
1462 void
flist_goto_by_path(view_t * view,const char path[])1463 flist_goto_by_path(view_t *view, const char path[])
1464 {
1465 char full_path[PATH_MAX + 1];
1466 const char *const name = get_last_path_component(path);
1467
1468 get_current_full_path(view, sizeof(full_path), full_path);
1469 if(stroscmp(full_path, path) == 0)
1470 {
1471 return;
1472 }
1473
1474 if(flist_custom_active(view) && cv_tree(view->custom.type) &&
1475 strcmp(name, "..") == 0)
1476 {
1477 int pos;
1478 char dir_only[PATH_MAX + 1];
1479
1480 snprintf(dir_only, sizeof(dir_only), "%.*s", (int)(name - path), path);
1481 pos = fpos_find_entry(view, name, dir_only);
1482 if(pos != -1)
1483 {
1484 view->list_pos = pos;
1485 }
1486 return;
1487 }
1488
1489 (void)set_position_by_path(view, path);
1490 }
1491
1492 dir_entry_t *
entry_from_path(view_t * view,dir_entry_t * entries,int count,const char path[])1493 entry_from_path(view_t *view, dir_entry_t *entries, int count,
1494 const char path[])
1495 {
1496 char canonic_path[PATH_MAX + 1];
1497 const char *fname;
1498 int i;
1499
1500 to_canonic_path(path, flist_get_dir(view), canonic_path,
1501 sizeof(canonic_path));
1502
1503 fname = get_last_path_component(canonic_path);
1504 for(i = 0; i < count; ++i)
1505 {
1506 char full_path[PATH_MAX + 1];
1507 dir_entry_t *const entry = &entries[i];
1508
1509 if(stroscmp(entry->name, fname) != 0)
1510 {
1511 continue;
1512 }
1513
1514 get_full_path_of(entry, sizeof(full_path), full_path);
1515 if(stroscmp(full_path, canonic_path) == 0)
1516 {
1517 return entry;
1518 }
1519 }
1520
1521 return NULL;
1522 }
1523
1524 uint64_t
fentry_get_nitems(const view_t * view,const dir_entry_t * entry)1525 fentry_get_nitems(const view_t *view, const dir_entry_t *entry)
1526 {
1527 uint64_t nitems;
1528 fentry_get_dir_info(view, entry, NULL, &nitems);
1529 return nitems;
1530 }
1531
1532 void
fentry_get_dir_info(const view_t * view,const dir_entry_t * entry,uint64_t * size,uint64_t * nitems)1533 fentry_get_dir_info(const view_t *view, const dir_entry_t *entry,
1534 uint64_t *size, uint64_t *nitems)
1535 {
1536 const int is_slow_fs = view->on_slow_fs;
1537 dcache_result_t size_res, nitems_res;
1538
1539 assert((size != NULL || nitems != NULL) &&
1540 "At least one of out parameters has to be non-NULL.");
1541
1542 dcache_get_of(entry, (size == NULL ? NULL : &size_res),
1543 (nitems == NULL ? NULL : &nitems_res));
1544
1545 if(size != NULL)
1546 {
1547 *size = size_res.value;
1548 if(size_res.value != DCACHE_UNKNOWN && !size_res.is_valid && !is_slow_fs)
1549 {
1550 *size = recalc_entry_size(entry, size_res.value);
1551 }
1552 }
1553
1554 if(nitems != NULL)
1555 {
1556 if(!nitems_res.is_valid && !is_slow_fs)
1557 {
1558 nitems_res.value = entry_calc_nitems(entry);
1559 }
1560
1561 *nitems = (nitems_res.value == DCACHE_UNKNOWN ? 0 : nitems_res.value);
1562 }
1563 }
1564
1565 /* Updates cached size of a directory also updating its relevant parents.
1566 * Returns current size of the directory entry. */
1567 static uint64_t
recalc_entry_size(const dir_entry_t * entry,uint64_t old_size)1568 recalc_entry_size(const dir_entry_t *entry, uint64_t old_size)
1569 {
1570 uint64_t size;
1571
1572 char full_path[PATH_MAX + 1];
1573 get_full_path_of(entry, sizeof(full_path), full_path);
1574
1575 size = fops_dir_size(full_path, 0, &ui_cancellation_info);
1576 dcache_update_parent_sizes(full_path, size - old_size);
1577
1578 return size;
1579 }
1580
1581 /* Calculates number of items at path specified by the entry. No check for file
1582 * type is performed. Returns the number, which is zero for files. */
1583 static uint64_t
entry_calc_nitems(const dir_entry_t * entry)1584 entry_calc_nitems(const dir_entry_t *entry)
1585 {
1586 char full_path[PATH_MAX + 1];
1587 get_full_path_of(entry, sizeof(full_path), full_path);
1588
1589 uint64_t ret = count_dir_items(full_path);
1590
1591 uint64_t inode = get_true_inode(entry);
1592 dcache_set_at(full_path, inode, DCACHE_UNKNOWN, ret);
1593
1594 return ret;
1595 }
1596
1597 int
populate_dir_list(view_t * view,int reload)1598 populate_dir_list(view_t *view, int reload)
1599 {
1600 const int result = populate_dir_list_internal(view, reload);
1601 if(view->list_pos > view->list_rows - 1)
1602 {
1603 view->list_pos = view->list_rows - 1;
1604 }
1605 return result;
1606 }
1607
1608 void
load_dir_list(view_t * view,int reload)1609 load_dir_list(view_t *view, int reload)
1610 {
1611 load_dir_list_internal(view, reload, 0);
1612 }
1613
1614 /* Loads file list for the view and redraws the view. The reload parameter
1615 * should be set in case of view refresh operation. The draw_only parameter
1616 * prevents extra actions that might be taken care of outside this function as
1617 * well. */
1618 static void
load_dir_list_internal(view_t * view,int reload,int draw_only)1619 load_dir_list_internal(view_t *view, int reload, int draw_only)
1620 {
1621 if(populate_dir_list(view, reload) != 0)
1622 {
1623 return;
1624 }
1625
1626 if(draw_only)
1627 {
1628 draw_dir_list_only(view);
1629 }
1630 else
1631 {
1632 draw_dir_list(view);
1633 }
1634
1635 if(view == curr_view)
1636 {
1637 if(strnoscmp(view->curr_dir, cfg.fuse_home, strlen(cfg.fuse_home)) == 0 &&
1638 stroscmp(other_view->curr_dir, view->curr_dir) == 0)
1639 {
1640 load_dir_list(other_view, 1);
1641 }
1642 }
1643 }
1644
1645 /* Loads filelist for the view. The reload parameter should be set in case of
1646 * view refresh operation. Returns non-zero on error. */
1647 static int
populate_dir_list_internal(view_t * view,int reload)1648 populate_dir_list_internal(view_t *view, int reload)
1649 {
1650 char *saved_cwd;
1651
1652 view->filtered = 0;
1653
1654 /* List reload usually implies that something related to file list has
1655 * changed, like an option. Reset cached lists to make sure they are up to
1656 * date with main column. */
1657 if(reload)
1658 {
1659 flist_free_cache(view, &view->left_column);
1660 flist_free_cache(view, &view->right_column);
1661 }
1662
1663 if(flist_custom_active(view))
1664 {
1665 return populate_custom_view(view, reload);
1666 }
1667
1668 if(!reload && is_dir_big(view->curr_dir))
1669 {
1670 if(!vle_mode_is(CMDLINE_MODE))
1671 {
1672 ui_sb_quick_msgf("%s", "Reading directory...");
1673 }
1674 }
1675
1676 if(curr_stats.load_stage < 2)
1677 {
1678 update_all_windows();
1679 }
1680
1681 saved_cwd = save_cwd();
1682 /* this is needed for lstat() below */
1683 if(vifm_chdir(view->curr_dir) != 0 && !is_unc_root(view->curr_dir))
1684 {
1685 LOG_SERROR_MSG(errno, "Can't chdir() into \"%s\"", view->curr_dir);
1686 restore_cwd(saved_cwd);
1687 return 1;
1688 }
1689
1690 /* If directory didn't change. */
1691 if(view->watch != NULL && view->watched_dir != NULL &&
1692 stroscmp(view->watched_dir, view->curr_dir) == 0)
1693 {
1694 int failed;
1695 /* Drain all events that happened before this point. */
1696 (void)fswatch_changed(view->watch, &failed);
1697 }
1698
1699 if(is_unc_root(view->curr_dir))
1700 {
1701 #ifdef _WIN32
1702 free_view_entries(view);
1703 if(fill_with_shared(view) == 0)
1704 {
1705 if(view->list_rows == 0)
1706 {
1707 add_parent_dir(view);
1708 }
1709 }
1710 else
1711 {
1712 show_error_msgf("Share enumeration error",
1713 "Can't load list of shares of %s", view->curr_dir);
1714
1715 leave_invalid_dir(view);
1716 if(update_dir_list(view, reload) != 0)
1717 {
1718 /* We don't have read access, only execute, or there were other
1719 * problems. */
1720 free_view_entries(view);
1721 add_parent_dir(view);
1722 }
1723 }
1724 #endif
1725 }
1726 else if(update_dir_list(view, reload) != 0)
1727 {
1728 /* We don't have read access, only execute, or there were other problems. */
1729 free_view_entries(view);
1730 add_parent_dir(view);
1731 }
1732
1733 if(!reload && !vle_mode_is(CMDLINE_MODE))
1734 {
1735 ui_sb_clear();
1736 }
1737
1738 fview_update_geometry(view);
1739
1740 /* If reloading the same directory don't jump to history position. Stay at
1741 * the current line. */
1742 if(!reload)
1743 {
1744 /* XXX: why cursor is positioned in code that loads the list? */
1745 flist_hist_lookup(view, view);
1746 }
1747
1748 if(view->location_changed)
1749 {
1750 fview_dir_updated(view);
1751 }
1752
1753 if(view->list_rows < 1)
1754 {
1755 if(rescue_from_empty_filelist(view))
1756 {
1757 restore_cwd(saved_cwd);
1758 return 0;
1759 }
1760
1761 add_parent_dir(view);
1762 }
1763
1764 fview_list_updated(view);
1765
1766 /* Because we reset directory watcher if directory didn't change before
1767 * loading file list, it's possible that we did load everything, but one more
1768 * update will happen anyway afterwards. We shouldn't drain change event
1769 * here, because it makes it possible to skip an update (when directory was
1770 * changed while we were reading from it). */
1771 update_dir_watcher(view);
1772
1773 if(view->location_changed)
1774 {
1775 view->location_changed = 0;
1776 vle_aucmd_execute("DirEnter", view->curr_dir, view);
1777 }
1778
1779 restore_cwd(saved_cwd);
1780 return 0;
1781 }
1782
1783 /* (Re)loads custom view file list. Returns non-zero on error. */
1784 static int
populate_custom_view(view_t * view,int reload)1785 populate_custom_view(view_t *view, int reload)
1786 {
1787 if(view->custom.type == CV_TREE)
1788 {
1789 dir_entry_t *prev_dir_entries;
1790 int prev_list_rows, result;
1791
1792 start_dir_list_change(view, &prev_dir_entries, &prev_list_rows, reload);
1793 result = flist_load_tree_internal(view, flist_get_dir(view), 1);
1794
1795 if(view->dir_entry == NULL)
1796 {
1797 /* Restore original list in case of failure. */
1798 view->dir_entry = prev_dir_entries;
1799 view->list_rows = result;
1800 }
1801 else
1802 {
1803 finish_dir_list_change(view, prev_dir_entries, prev_list_rows);
1804 }
1805
1806 if(result != 0)
1807 {
1808 show_error_msg("Tree View", "Reload failed");
1809 }
1810
1811 return result;
1812 }
1813
1814 if(view->custom.type == CV_DIFF)
1815 {
1816 if(filter_in_compare(view, NULL, &entry_exists))
1817 {
1818 return 0;
1819 }
1820 }
1821 else
1822 {
1823 if(custom_list_is_incomplete(view))
1824 {
1825 dir_entry_t *prev_dir_entries;
1826 int prev_list_rows;
1827
1828 start_dir_list_change(view, &prev_dir_entries, &prev_list_rows, reload);
1829
1830 replace_dir_entries(view, &view->dir_entry, &view->list_rows,
1831 view->local_filter.entries, view->local_filter.entry_count);
1832 /* Selection of the original list shouldn't be restored. */
1833 flist_sel_drop(view);
1834
1835 /* We're merging instead of simply replacing entries to account for
1836 * selection and possibly other attributes of entries. Merging also takes
1837 * care of cursor position. */
1838 finish_dir_list_change(view, prev_dir_entries, prev_list_rows);
1839 }
1840 else if(view->local_filter.entry_count == 0)
1841 {
1842 /* Save unfiltered (by local filter) list for further use. */
1843 replace_dir_entries(view, &view->local_filter.entries,
1844 &view->local_filter.entry_count, view->dir_entry, view->list_rows);
1845 }
1846
1847 (void)zap_entries(view, view->dir_entry, &view->list_rows,
1848 &is_dead_or_filtered, NULL, 0, 0);
1849 }
1850
1851 update_entries_data(view);
1852 sort_dir_list(!reload, view);
1853 fview_list_updated(view);
1854 return 0;
1855 }
1856
1857 int
filter_in_compare(view_t * view,void * arg,zap_filter filter)1858 filter_in_compare(view_t *view, void *arg, zap_filter filter)
1859 {
1860 view_t *const other = (view == curr_view) ? other_view : curr_view;
1861
1862 zap_compare_view(view, other, filter, arg);
1863 if(view->list_rows == 0)
1864 {
1865 /* Load views before showing the message as event loop in message dialog can
1866 * try to reload views. */
1867 rn_leave(view, 1);
1868 show_error_msg("Comparison", "No files left in the views, left the mode.");
1869 return 1;
1870 }
1871
1872 exclude_temporary_entries(other);
1873 return 0;
1874 }
1875
1876 /* Checks whether entry refers to non-existing file. Returns non-zero if so,
1877 * otherwise zero is returned. */
1878 static int
entry_exists(view_t * view,const dir_entry_t * entry,void * arg)1879 entry_exists(view_t *view, const dir_entry_t *entry, void *arg)
1880 {
1881 return path_exists_at(entry->origin, entry->name, NODEREF);
1882 }
1883
1884 /* Removes entries that refer to non-existing files from compare view marking
1885 * corresponding entries of the other view as temporary. This is a
1886 * zap_entries() tailored for needs of compare, because that function is already
1887 * too complex. */
1888 static void
zap_compare_view(view_t * view,view_t * other,zap_filter filter,void * arg)1889 zap_compare_view(view_t *view, view_t *other, zap_filter filter, void *arg)
1890 {
1891 int i, j = 0;
1892
1893 for(i = 0; i < view->list_rows; ++i)
1894 {
1895 dir_entry_t *const entry = &view->dir_entry[i];
1896
1897 if(!fentry_is_fake(entry) && !filter(view, entry, arg))
1898 {
1899 if(entry->selected)
1900 {
1901 --view->selected_files;
1902 }
1903
1904 const int separator = find_separator(other, i);
1905 if(separator >= 0)
1906 {
1907 fentry_free(view, entry);
1908 other->dir_entry[separator].temporary = 1;
1909
1910 if(view->list_pos == i)
1911 {
1912 view->list_pos = j;
1913 }
1914 continue;
1915 }
1916 replace_string(&entry->name, "");
1917 entry->type = FT_UNK;
1918 entry->id = other->dir_entry[i].id;
1919 }
1920
1921 if(i != j)
1922 {
1923 view->dir_entry[j] = view->dir_entry[i];
1924 }
1925
1926 ++j;
1927 }
1928
1929 view->list_rows = j;
1930 }
1931
1932 /* Finds separator among the group of equivalent files of the view specified by
1933 * its position. Returns index of the separator or -1. */
1934 static int
find_separator(view_t * view,int idx)1935 find_separator(view_t *view, int idx)
1936 {
1937 int i;
1938 const int id = view->dir_entry[idx].id;
1939
1940 for(i = idx; i >= 0 && view->dir_entry[i].id == id; --i)
1941 {
1942 if(!view->dir_entry[i].temporary && fentry_is_fake(&view->dir_entry[i]))
1943 {
1944 return i;
1945 }
1946 }
1947 for(i = idx + 1; i < view->list_rows && view->dir_entry[i].id == id; ++i)
1948 {
1949 if(!view->dir_entry[i].temporary && fentry_is_fake(&view->dir_entry[i]))
1950 {
1951 return i;
1952 }
1953 }
1954 return -1;
1955 }
1956
1957 /* Updates directory watcher of the view. */
1958 static void
update_dir_watcher(view_t * view)1959 update_dir_watcher(view_t *view)
1960 {
1961 const char *const curr_dir = flist_get_dir(view);
1962
1963 if(view->watch == NULL || view->watched_dir == NULL ||
1964 stroscmp(view->watched_dir, curr_dir) != 0)
1965 {
1966 fswatch_free(view->watch);
1967 view->watch = fswatch_create(curr_dir);
1968
1969 /* Failure to create a watch is bad, but there isn't much we can do here and
1970 * this doesn't feel like a reason to block anything else. */
1971 if(view->watch != NULL)
1972 {
1973 replace_string(&view->watched_dir, curr_dir);
1974 }
1975 }
1976 }
1977
1978 /* Checks whether currently loaded custom list of files is missing some files
1979 * compared to the original custom list. Returns non-zero if so, otherwise zero
1980 * is returned. */
1981 static int
custom_list_is_incomplete(const view_t * view)1982 custom_list_is_incomplete(const view_t *view)
1983 {
1984 if(view->local_filter.entry_count == 0)
1985 {
1986 return 0;
1987 }
1988
1989 if(view->list_rows == 1 && is_parent_dir(view->dir_entry[0].name) &&
1990 !(view->local_filter.entry_count == 1 &&
1991 is_parent_dir(view->local_filter.entries[0].name)))
1992 {
1993 return 1;
1994 }
1995
1996 return view->list_rows != view->local_filter.entry_count;
1997 }
1998
1999 /* zap_entries() filter to filter-out inexistent files or files which names
2000 * match local filter. */
2001 static int
is_dead_or_filtered(view_t * view,const dir_entry_t * entry,void * arg)2002 is_dead_or_filtered(view_t *view, const dir_entry_t *entry, void *arg)
2003 {
2004 if(!path_exists_at(entry->origin, entry->name, NODEREF))
2005 {
2006 return 0;
2007 }
2008
2009 if(local_filter_matches(view, entry))
2010 {
2011 return 1;
2012 }
2013
2014 ++view->filtered;
2015 return 0;
2016 }
2017
2018 /* Re-read meta-data for each entry (does nothing for entries on which querying
2019 * fails). */
2020 static void
update_entries_data(view_t * view)2021 update_entries_data(view_t *view)
2022 {
2023 int i;
2024 for(i = 0; i < view->list_rows; ++i)
2025 {
2026 char full_path[PATH_MAX + 1];
2027 dir_entry_t *const entry = &view->dir_entry[i];
2028
2029 /* Fake entries do not map onto files in file system. */
2030 if(fentry_is_fake(entry))
2031 {
2032 continue;
2033 }
2034
2035 get_full_path_of(entry, sizeof(full_path), full_path);
2036
2037 /* Do not care about possible failure, just use previous meta-data. */
2038 (void)fill_dir_entry_by_path(entry, full_path);
2039 }
2040 }
2041
2042 int
zap_entries(view_t * view,dir_entry_t * entries,int * count,zap_filter filter,void * arg,int allow_empty_list,int remove_subtrees)2043 zap_entries(view_t *view, dir_entry_t *entries, int *count, zap_filter filter,
2044 void *arg, int allow_empty_list, int remove_subtrees)
2045 {
2046 int i, j;
2047
2048 j = 0;
2049 for(i = 0; i < *count; ++i)
2050 {
2051 int k, pos, parent;
2052 dir_entry_t *const entry = &entries[i];
2053 const int nremoved = remove_subtrees ? (entry->child_count + 1) : 1;
2054
2055 if(filter(view, entry, arg))
2056 {
2057 /* We're keeping this entry. */
2058 if(i != j)
2059 {
2060 entries[j] = entries[i];
2061 }
2062
2063 ++j;
2064 continue;
2065 }
2066
2067 if(entry->selected && view->dir_entry == entries)
2068 {
2069 --view->selected_files;
2070 }
2071
2072 /* Reassign children of node about to be deleted to its parent. Child count
2073 * don't need an update here because these nodes are already counted. */
2074 pos = i + 1;
2075 while(pos < i + 1 + entry->child_count)
2076 {
2077 if(entry->child_pos == 0)
2078 {
2079 entries[pos].child_pos = 0;
2080 }
2081 else
2082 {
2083 entries[pos].child_pos += entry->child_pos - 1;
2084 }
2085 pos += entries[pos].child_count + 1;
2086 }
2087
2088 if(entry->child_pos != 0)
2089 {
2090 /* Visit all parent nodes to update number of their children and also
2091 * all sibling nodes which require their parent links updated. */
2092 int pos = i + (entry->child_count + 1);
2093 int parent = j - entry->child_pos;
2094 while(1)
2095 {
2096 while(pos <= parent + (i - j) + entries[parent].child_count)
2097 {
2098 entries[pos].child_pos -= nremoved;
2099 pos += entries[pos].child_count + 1;
2100 }
2101 entries[parent].child_count -= nremoved;
2102 if(entries[parent].child_pos == 0)
2103 {
2104 break;
2105 }
2106 parent -= entries[parent].child_pos;
2107 }
2108 }
2109
2110 for(k = 0; k < nremoved; ++k)
2111 {
2112 fentry_free(view, &entry[k]);
2113 }
2114
2115 /* If we're removing file from main list of entries and cursor is right on
2116 * this file, move cursor at position this file would take in resulting
2117 * list. */
2118 if(entries == view->dir_entry && view->list_pos >= i &&
2119 view->list_pos < i + nremoved)
2120 {
2121 view->list_pos = j;
2122 }
2123
2124 /* Add directory leaf if we just removed last child of the last of nodes
2125 * that wasn't filtered. We can use one entry because if something was
2126 * filtered out, there is space for at least one extra entry. */
2127 parent = i - entry->child_pos - (i - j);
2128 int show_empty_dir_leafs = (cfg.dot_dirs & DD_TREE_LEAFS_PARENT);
2129 if(show_empty_dir_leafs && remove_subtrees && parent == j - 1 &&
2130 entries[parent].child_count == 0)
2131 {
2132 char full_path[PATH_MAX + 1];
2133 char *path;
2134
2135 int pos = i + (entry->child_count + 1);
2136
2137 get_full_path_of(&entries[j - 1], sizeof(full_path), full_path);
2138 path = format_str("%s/..", full_path);
2139 init_parent_entry(view, &entries[j], path);
2140 remove_last_path_component(path);
2141 entries[j].origin = path;
2142 entries[j].child_pos = 1;
2143
2144 /* Since we now adding back one entry, correct increase parent counts and
2145 * child positions back by one. */
2146 while(1)
2147 {
2148 while(pos <= parent + (i - j + nremoved) + entries[parent].child_count)
2149 {
2150 ++entries[pos].child_pos;
2151 pos += entries[pos].child_count + 1;
2152 }
2153 ++entries[parent].child_count;
2154 if(entries[parent].child_pos == 0)
2155 {
2156 break;
2157 }
2158 parent -= entries[parent].child_pos;
2159 }
2160
2161 ++j;
2162 }
2163
2164 i += nremoved - 1;
2165 }
2166
2167 *count = j;
2168
2169 if(*count == 0 && !allow_empty_list)
2170 {
2171 add_parent_dir(view);
2172 }
2173
2174 return i - j;
2175 }
2176
2177 /* Checks for subjectively relative size of a directory specified by the path
2178 * parameter. Returns non-zero if size of the directory in question is
2179 * considered to be big. */
2180 static int
is_dir_big(const char path[])2181 is_dir_big(const char path[])
2182 {
2183 #ifndef _WIN32
2184 struct stat s;
2185 if(os_stat(path, &s) != 0)
2186 {
2187 LOG_SERROR_MSG(errno, "Can't stat() \"%s\"", path);
2188 return 1;
2189 }
2190 return s.st_size > s.st_blksize;
2191 #else
2192 return 1;
2193 #endif
2194 }
2195
2196 /* Frees list of directory entries of the view. */
2197 static void
free_view_entries(view_t * view)2198 free_view_entries(view_t *view)
2199 {
2200 free_dir_entries(view, &view->dir_entry, &view->list_rows);
2201 }
2202
2203 /* Updates file list with files from current directory. Returns zero on
2204 * success, otherwise non-zero is returned. */
2205 static int
update_dir_list(view_t * view,int reload)2206 update_dir_list(view_t *view, int reload)
2207 {
2208 dir_entry_t *prev_dir_entries;
2209 int prev_list_rows;
2210
2211 start_dir_list_change(view, &prev_dir_entries, &prev_list_rows, reload);
2212
2213 if(enum_dir_content(view->curr_dir, &add_file_entry_to_view, view) != 0)
2214 {
2215 LOG_SERROR_MSG(errno, "Can't opendir() \"%s\"", view->curr_dir);
2216 free_dir_entries(view, &prev_dir_entries, &prev_list_rows);
2217 return 1;
2218 }
2219
2220 if(cfg_parent_dir_is_visible(is_root_dir(view->curr_dir)) ||
2221 view->list_rows == 0)
2222 {
2223 add_parent_dir(view);
2224 }
2225
2226 sort_dir_list(!reload, view);
2227
2228 /* Merging must be performed after sorting so that list position remains fixed
2229 * (sorting doesn't preserve it). */
2230 finish_dir_list_change(view, prev_dir_entries, prev_list_rows);
2231
2232 return 0;
2233 }
2234
2235 /* Starts file list update, saving previous list for future reference if
2236 * necessary. */
2237 static void
start_dir_list_change(view_t * view,dir_entry_t ** entries,int * len,int reload)2238 start_dir_list_change(view_t *view, dir_entry_t **entries, int *len, int reload)
2239 {
2240 if(reload)
2241 {
2242 *entries = view->dir_entry;
2243 *len = view->list_rows;
2244 view->dir_entry = NULL;
2245 view->list_rows = 0;
2246 }
2247 else
2248 {
2249 *entries = NULL;
2250 *len = 0;
2251 free_view_entries(view);
2252 }
2253
2254 view->matches = 0;
2255 view->selected_files = 0;
2256 }
2257
2258 /* Finishes file list update, possibly merging information from old entries into
2259 * new ones. */
2260 static void
finish_dir_list_change(view_t * view,dir_entry_t * entries,int len)2261 finish_dir_list_change(view_t *view, dir_entry_t *entries, int len)
2262 {
2263 check_file_uniqueness(view);
2264
2265 if(entries != NULL)
2266 {
2267 merge_lists(view, entries, len);
2268 free_dir_entries(view, &entries, &len);
2269 }
2270
2271 view->dir_entry = dynarray_shrink(view->dir_entry);
2272 }
2273
2274 /* enum_dir_content() callback that appends files to file list. Returns zero on
2275 * success or non-zero to indicate failure and stop enumeration. */
2276 static int
add_file_entry_to_view(const char name[],const void * data,void * param)2277 add_file_entry_to_view(const char name[], const void *data, void *param)
2278 {
2279 view_t *const view = param;
2280 dir_entry_t *entry;
2281
2282 /* Always ignore the "." and ".." directories. */
2283 if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
2284 {
2285 return 0;
2286 }
2287
2288 if(!file_is_visible(view, name, 0, data, 1))
2289 {
2290 ++view->filtered;
2291 return 0;
2292 }
2293
2294 entry = alloc_dir_entry(&view->dir_entry, view->list_rows);
2295 if(entry == NULL)
2296 {
2297 show_error_msg("Memory Error", "Unable to allocate enough memory");
2298 return 1;
2299 }
2300
2301 init_dir_entry(view, entry, name);
2302
2303 if(fill_dir_entry(entry, entry->name, data) == 0)
2304 {
2305 ++view->list_rows;
2306 }
2307 else
2308 {
2309 fentry_free(view, entry);
2310 }
2311
2312 return 0;
2313 }
2314
2315 void
resort_dir_list(int msg,view_t * view)2316 resort_dir_list(int msg, view_t *view)
2317 {
2318 char full_path[PATH_MAX + 1];
2319 const int top_delta = view->list_pos - view->top_line;
2320 if(view->list_pos < view->list_rows)
2321 {
2322 get_current_full_path(view, sizeof(full_path), full_path);
2323 }
2324
2325 sort_dir_list(msg, view);
2326
2327 if(view->list_pos < view->list_rows)
2328 {
2329 flist_goto_by_path(view, full_path);
2330 view->top_line = view->list_pos - top_delta;
2331 }
2332 }
2333
2334 /* Resorts view without reloading it. msg parameter controls whether to show
2335 * "Sorting..." statusbar message. */
2336 static void
sort_dir_list(int msg,view_t * view)2337 sort_dir_list(int msg, view_t *view)
2338 {
2339 if(msg && view->list_rows > 2048 && !vle_mode_is(CMDLINE_MODE))
2340 {
2341 ui_sb_quick_msgf("%s", "Sorting directory...");
2342 }
2343
2344 sort_view(view);
2345
2346 if(msg && !vle_mode_is(CMDLINE_MODE))
2347 {
2348 ui_sb_clear();
2349 }
2350 }
2351
2352 /* Merges elements from previous list into the new one. */
2353 static void
merge_lists(view_t * view,dir_entry_t * entries,int len)2354 merge_lists(view_t *view, dir_entry_t *entries, int len)
2355 {
2356 int i;
2357 int closest_dist;
2358 const int prev_pos = view->list_pos;
2359 trie_t *prev_names = trie_create();
2360
2361 for(i = 0; i < len; ++i)
2362 {
2363 add_to_trie(prev_names, view, &entries[i]);
2364
2365 /* We won't use the name later, so free some memory. */
2366 update_string(&entries[i].name, NULL);
2367 }
2368
2369 closest_dist = INT_MIN;
2370 for(i = 0; i < view->list_rows; ++i)
2371 {
2372 int dist;
2373 void *data;
2374 dir_entry_t *const entry = &view->dir_entry[i];
2375 if(!is_in_trie(prev_names, view, entry, &data))
2376 {
2377 continue;
2378 }
2379
2380 /* Transfer information from previous entry to the new one. */
2381 merge_entries(entry, data);
2382
2383 /* Update number of selected files (should have been zeroed beforehand). */
2384 view->selected_files += (entry->selected != 0);
2385
2386 /* Update cursor position in a smart way. */
2387 dist = (dir_entry_t *)data - entries - prev_pos;
2388 closest_dist = correct_pos(view, i, dist, closest_dist);
2389 }
2390
2391 trie_free(prev_names);
2392 }
2393
2394 /* Checks that entries don't have the same name (for non-cv). And if there are
2395 * duplicates shows a warning to the user (shown each time broken directory is
2396 * entered) and drops duplicates. */
2397 TSTATIC void
check_file_uniqueness(view_t * view)2398 check_file_uniqueness(view_t *view)
2399 {
2400 trie_t *file_names = trie_create();
2401
2402 int had_dups = view->has_dups;
2403
2404 int i;
2405 for(i = 0; i < view->list_rows; ++i)
2406 {
2407 add_to_trie(file_names, view, &view->dir_entry[i]);
2408 }
2409
2410 trie_free(file_names);
2411
2412 if(view->has_dups)
2413 {
2414 (void)exclude_temporary_entries(view);
2415 if(!had_dups)
2416 {
2417 show_error_msg("Broken File System", "Underlying file system seems to "
2418 "report duplicated file names. Only one entry will be shown.");
2419 }
2420 }
2421 }
2422
2423 /* Adds view entry into the trie mapping its name to entry structure.
2424 * Duplicated entries of a non-custom view are marked as temporary. */
2425 static void
add_to_trie(trie_t * trie,view_t * view,dir_entry_t * entry)2426 add_to_trie(trie_t *trie, view_t *view, dir_entry_t *entry)
2427 {
2428 if(flist_custom_active(view))
2429 {
2430 char full_path[PATH_MAX + 1];
2431 get_full_path_of(entry, sizeof(full_path), full_path);
2432
2433 int error = trie_set(trie, full_path, entry);
2434 assert(error == 0 && "Duplicated file names in the list?");
2435 (void)error;
2436 return;
2437 }
2438
2439 if(trie_set(trie, entry->name, entry) != 0)
2440 {
2441 LOG_INFO_MSG("Duplicated entry is `%s` in `%s`", entry->name,
2442 entry->origin);
2443 entry->temporary = 1;
2444 view->has_dups = 1;
2445 }
2446 }
2447
2448 /* Looks up entry in the trie by its name. Retrieves directory entry stored by
2449 * add_to_trie() into *data (unchanged on lookup failure). Returns non-zero if
2450 * item was successfully retrieved and zero otherwise. */
2451 static int
is_in_trie(trie_t * trie,view_t * view,dir_entry_t * entry,void ** data)2452 is_in_trie(trie_t *trie, view_t *view, dir_entry_t *entry, void **data)
2453 {
2454 int error;
2455
2456 if(flist_custom_active(view))
2457 {
2458 char full_path[PATH_MAX + 1];
2459 get_full_path_of(entry, sizeof(full_path), full_path);
2460 error = trie_get(trie, full_path, data);
2461 }
2462 else
2463 {
2464 error = trie_get(trie, entry->name, data);
2465 }
2466
2467 return (error == 0);
2468 }
2469
2470 /* Merges data from previous entry into the new one. Both entries should
2471 * correspond to the same file (their names must match). */
2472 static void
merge_entries(dir_entry_t * new,const dir_entry_t * prev)2473 merge_entries(dir_entry_t *new, const dir_entry_t *prev)
2474 {
2475 new->id = prev->id;
2476
2477 new->selected = prev->selected;
2478 new->was_selected = prev->was_selected;
2479
2480 /* No need to check for name here, because only entries with exactly the same
2481 * names are merged. */
2482 if(new->type == prev->type)
2483 {
2484 new->hi_num = prev->hi_num;
2485 new->name_dec_num = prev->name_dec_num;
2486 }
2487 }
2488
2489 /* Corrects selected item position in the list. Returns updated value of the
2490 * closest variable, which initially should be INT_MIN. */
2491 static int
correct_pos(view_t * view,int pos,int dist,int closest)2492 correct_pos(view_t *view, int pos, int dist, int closest)
2493 {
2494 if(dist == 0)
2495 {
2496 closest = 0;
2497 view->list_pos = pos;
2498 }
2499 else if((closest < 0 && dist > closest) || (closest > 0 && dist < closest))
2500 {
2501 closest = dist;
2502 view->list_pos = pos;
2503 }
2504 return closest;
2505 }
2506
2507 /* Performs actions needed to rescue from abnormal situation with empty
2508 * filelist. Returns non-zero if file list was reloaded. */
2509 static int
rescue_from_empty_filelist(view_t * view)2510 rescue_from_empty_filelist(view_t *view)
2511 {
2512 /* It is possible to set the file name filter so that no files are showing
2513 * in the / directory. All other directories will always show at least the
2514 * ../ file. This resets the filter and reloads the directory. */
2515 if(name_filters_empty(view))
2516 {
2517 return 0;
2518 }
2519
2520 show_error_msgf("Filter error",
2521 "The %s\"%s\" pattern did not match any files. It was reset.",
2522 view->invert ? "" : "inverted ", matcher_get_expr(view->manual_filter));
2523
2524 name_filters_drop(view);
2525
2526 load_dir_list(view, 1);
2527 if(view->list_rows < 1)
2528 {
2529 leave_invalid_dir(view);
2530 (void)change_directory(view, view->curr_dir);
2531 load_dir_list(view, 0);
2532 }
2533
2534 return 1;
2535 }
2536
2537 void
add_parent_dir(view_t * view)2538 add_parent_dir(view_t *view)
2539 {
2540 add_parent_entry(view, &view->dir_entry, &view->list_rows);
2541 }
2542
2543 /* Adds parent directory entry (..) to specified list of entries. */
2544 static void
add_parent_entry(view_t * view,dir_entry_t ** entries,int * count)2545 add_parent_entry(view_t *view, dir_entry_t **entries, int *count)
2546 {
2547 dir_entry_t *const dir_entry = alloc_dir_entry(entries, *count);
2548 if(dir_entry == NULL)
2549 {
2550 show_error_msg("Memory Error", "Unable to allocate enough memory");
2551 return;
2552 }
2553
2554 if(init_parent_entry(view, dir_entry, "..") == 0)
2555 {
2556 ++*count;
2557 }
2558 }
2559
2560 /* Initializes dir_entry_t with name and all other fields with default
2561 * values. */
2562 static void
init_dir_entry(view_t * view,dir_entry_t * entry,const char name[])2563 init_dir_entry(view_t *view, dir_entry_t *entry, const char name[])
2564 {
2565 entry->name = strdup(name);
2566 entry->origin = &view->curr_dir[0];
2567
2568 entry->size = 0ULL;
2569 #ifndef _WIN32
2570 entry->uid = (uid_t)-1;
2571 entry->gid = (gid_t)-1;
2572 entry->mode = (mode_t)0;
2573 entry->inode = (ino_t)0;
2574 #else
2575 entry->attrs = 0;
2576 #endif
2577
2578 entry->mtime = (time_t)0;
2579 entry->atime = (time_t)0;
2580 entry->ctime = (time_t)0;
2581
2582 entry->type = FT_UNK;
2583 entry->nlinks = 0;
2584 entry->dir_link = 0;
2585 entry->hi_num = -1;
2586 entry->name_dec_num = -1;
2587
2588 entry->child_count = 0;
2589 entry->child_pos = 0;
2590
2591 /* All files start as unselected, unmatched and unmarked. */
2592 entry->selected = 0;
2593 entry->was_selected = 0;
2594 entry->search_match = 0;
2595 entry->marked = 0;
2596 entry->temporary = 0;
2597
2598 entry->tag = -1;
2599 entry->id = -1;
2600 }
2601
2602 void
replace_dir_entries(view_t * view,dir_entry_t ** entries,int * count,const dir_entry_t * with_entries,int with_count)2603 replace_dir_entries(view_t *view, dir_entry_t **entries, int *count,
2604 const dir_entry_t *with_entries, int with_count)
2605 {
2606 dir_entry_t *new;
2607 int i;
2608
2609 new = dynarray_extend(NULL, with_count*sizeof(*new));
2610 if(new == NULL)
2611 {
2612 return;
2613 }
2614
2615 memcpy(new, with_entries, sizeof(*new)*with_count);
2616
2617 for(i = 0; i < with_count; ++i)
2618 {
2619 dir_entry_t *const entry = &new[i];
2620
2621 entry->name = strdup(entry->name);
2622 entry->origin = strdup(entry->origin);
2623
2624 if(entry->name == NULL || entry->origin == NULL)
2625 {
2626 int count_so_far = i + 1;
2627 free_dir_entries(view, &new, &count_so_far);
2628 return;
2629 }
2630 }
2631
2632 free_dir_entries(view, entries, count);
2633 *entries = new;
2634 *count = with_count;
2635 }
2636
2637 void
free_dir_entries(view_t * view,dir_entry_t ** entries,int * count)2638 free_dir_entries(view_t *view, dir_entry_t **entries, int *count)
2639 {
2640 int i;
2641 for(i = 0; i < *count; ++i)
2642 {
2643 fentry_free(view, &(*entries)[i]);
2644 }
2645
2646 dynarray_free(*entries);
2647 *entries = NULL;
2648 *count = 0;
2649 }
2650
2651 void
fentry_free(const view_t * view,dir_entry_t * entry)2652 fentry_free(const view_t *view, dir_entry_t *entry)
2653 {
2654 free(entry->name);
2655 entry->name = NULL;
2656
2657 if(entry->origin != &lwin.curr_dir[0] && entry->origin != &rwin.curr_dir[0])
2658 {
2659 free(entry->origin);
2660 entry->origin = NULL;
2661 }
2662 }
2663
2664 dir_entry_t *
add_dir_entry(dir_entry_t ** list,size_t * list_size,const dir_entry_t * entry)2665 add_dir_entry(dir_entry_t **list, size_t *list_size, const dir_entry_t *entry)
2666 {
2667 dir_entry_t *const new_entry = alloc_dir_entry(list, *list_size);
2668 if(new_entry == NULL)
2669 {
2670 return NULL;
2671 }
2672
2673 *new_entry = *entry;
2674 ++*list_size;
2675 return new_entry;
2676 }
2677
2678 dir_entry_t *
entry_list_add(view_t * view,dir_entry_t ** list,int * list_size,const char path[])2679 entry_list_add(view_t *view, dir_entry_t **list, int *list_size,
2680 const char path[])
2681 {
2682 dir_entry_t *const dir_entry = alloc_dir_entry(list, *list_size);
2683 if(dir_entry == NULL)
2684 {
2685 return NULL;
2686 }
2687
2688 init_dir_entry(view, dir_entry, get_last_path_component(path));
2689
2690 dir_entry->origin = strdup(path);
2691 remove_last_path_component(dir_entry->origin);
2692
2693 if(fill_dir_entry_by_path(dir_entry, path) != 0)
2694 {
2695 fentry_free(view, dir_entry);
2696 return NULL;
2697 }
2698
2699 ++*list_size;
2700 return dir_entry;
2701 }
2702
2703 /* Allocates one more directory entry for the *list of size list_size by
2704 * extending it. Returns pointer to new entry or NULL on failure. */
2705 static dir_entry_t *
alloc_dir_entry(dir_entry_t ** list,int list_size)2706 alloc_dir_entry(dir_entry_t **list, int list_size)
2707 {
2708 dir_entry_t *new_entry_list = dynarray_extend(*list, sizeof(dir_entry_t));
2709 if(new_entry_list == NULL)
2710 {
2711 return NULL;
2712 }
2713
2714 *list = new_entry_list;
2715 return &new_entry_list[list_size];
2716 }
2717
2718 void
check_if_filelist_has_changed(view_t * view)2719 check_if_filelist_has_changed(view_t *view)
2720 {
2721 int failed, changed;
2722 const char *const curr_dir = flist_get_dir(view);
2723
2724 if(view->on_slow_fs ||
2725 (flist_custom_active(view) && !cv_tree(view->custom.type)) ||
2726 is_unc_root(curr_dir))
2727 {
2728 return;
2729 }
2730
2731 if(view->watch == NULL)
2732 {
2733 /* If watch is not initialized, try to do this, but don't fail on error. */
2734
2735 update_dir_watcher(view);
2736 failed = 0;
2737 changed = (view->watch != NULL);
2738 }
2739 else
2740 {
2741 changed = fswatch_changed(view->watch, &failed);
2742 }
2743
2744 /* Check if we still have permission to visit this directory. */
2745 failed |= (os_access(curr_dir, X_OK) != 0);
2746
2747 if(failed)
2748 {
2749 LOG_SERROR_MSG(errno, "Can't stat() \"%s\"", curr_dir);
2750 log_cwd();
2751
2752 show_error_msgf("Directory Change Check", "Cannot open %s", curr_dir);
2753
2754 leave_invalid_dir(view);
2755 (void)change_directory(view, curr_dir);
2756 flist_sel_stash(view);
2757 ui_view_schedule_reload(view);
2758 return;
2759 }
2760
2761 if(changed)
2762 {
2763 ui_view_schedule_reload(view);
2764 }
2765 else if(flist_custom_active(view) && cv_tree(view->custom.type))
2766 {
2767 /* Custom trees don't track file-system changes. */
2768 if(view->custom.type == CV_TREE &&
2769 tree_has_changed(view->dir_entry, view->list_rows))
2770 {
2771 ui_view_schedule_reload(view);
2772 }
2773 }
2774 else
2775 {
2776 if(flist_update_cache(view, &view->left_column, view->left_column.dir) ||
2777 flist_update_cache(view, &view->right_column, view->right_column.dir))
2778 {
2779 ui_view_schedule_redraw(view);
2780 }
2781 }
2782 }
2783
2784 /* Checks whether tree-view needs a reload (any of subdirectories were changed).
2785 * Returns non-zero if so, otherwise zero is returned. */
2786 static int
tree_has_changed(const dir_entry_t * entries,size_t nchildren)2787 tree_has_changed(const dir_entry_t *entries, size_t nchildren)
2788 {
2789 size_t pos = 0U;
2790 while(pos < nchildren)
2791 {
2792 const dir_entry_t *const entry = &entries[pos];
2793 if(entry->type == FT_DIR && !is_parent_dir(entry->name))
2794 {
2795 char full_path[PATH_MAX + 1];
2796 struct stat s;
2797
2798 get_full_path_of(entry, sizeof(full_path), full_path);
2799 if(os_stat(full_path, &s) != 0 || entry->mtime != s.st_mtime)
2800 {
2801 return 1;
2802 }
2803
2804 if(tree_has_changed(entry + 1, entry->child_count))
2805 {
2806 return 1;
2807 }
2808 }
2809
2810 pos += entry->child_count + 1;
2811 }
2812 return 0;
2813 }
2814
2815 int
flist_update_cache(view_t * view,cached_entries_t * cache,const char path[])2816 flist_update_cache(view_t *view, cached_entries_t *cache, const char path[])
2817 {
2818 int update = 0;
2819 int error;
2820
2821 if(path == NULL)
2822 {
2823 return 0;
2824 }
2825
2826 if(cache->watch == NULL || stroscmp(cache->dir, path) != 0)
2827 {
2828 fswatch_free(cache->watch);
2829
2830 cache->watch = fswatch_create(path);
2831 if(cache->watch == NULL)
2832 {
2833 /* Reset the cache on failure to create a watcher to do not accidentally
2834 * provide incorrect data. */
2835 flist_free_cache(view, cache);
2836 return 0;
2837 }
2838
2839 replace_string(&cache->dir, path);
2840
2841 update = 1;
2842 }
2843
2844 if(update || fswatch_changed(cache->watch, &error) || error)
2845 {
2846 free_dir_entries(view, &cache->entries.entries, &cache->entries.nentries);
2847 cache->entries = flist_list_in(view, path, 0, 1);
2848 return 1;
2849 }
2850
2851 return 0;
2852 }
2853
2854 void
flist_free_cache(view_t * view,cached_entries_t * cache)2855 flist_free_cache(view_t *view, cached_entries_t *cache)
2856 {
2857 free_dir_entries(view, &cache->entries.entries, &cache->entries.nentries);
2858 update_string(&cache->dir, NULL);
2859 fswatch_free(cache->watch);
2860 cache->watch = NULL;
2861 }
2862
2863 void
flist_update_origins(view_t * view,const char from[],char to[])2864 flist_update_origins(view_t *view, const char from[], char to[])
2865 {
2866 int i;
2867 for(i = 0; i < view->list_rows; ++i)
2868 {
2869 dir_entry_t *const entry = &view->dir_entry[i];
2870 if(entry->origin == from)
2871 {
2872 entry->origin = to;
2873 }
2874 }
2875 }
2876
2877 int
cd_is_possible(const char path[])2878 cd_is_possible(const char path[])
2879 {
2880 if(!is_valid_dir(path))
2881 {
2882 LOG_SERROR_MSG(errno, "Can't access \"%s\"", path);
2883
2884 show_error_msgf("Destination doesn't exist or isn't a directory", "\"%s\"",
2885 path);
2886 return 0;
2887 }
2888 else if(!directory_accessible(path))
2889 {
2890 LOG_SERROR_MSG(errno, "Can't access(, X_OK) \"%s\"", path);
2891
2892 show_error_msgf("Permission denied", "\"%s\"", path);
2893 return 0;
2894 }
2895 else
2896 {
2897 return 1;
2898 }
2899 }
2900
2901 void
load_saving_pos(view_t * view)2902 load_saving_pos(view_t *view)
2903 {
2904 char full_path[PATH_MAX + 1];
2905
2906 if(curr_stats.load_stage < 2)
2907 return;
2908
2909 if(!window_shows_dirlist(view))
2910 return;
2911
2912 if(view->local_filter.in_progress)
2913 {
2914 return;
2915 }
2916
2917 get_current_full_path(view, sizeof(full_path), full_path);
2918
2919 load_dir_list_internal(view, 1, 1);
2920
2921 flist_goto_by_path(view, full_path);
2922
2923 fview_cursor_redraw(view);
2924
2925 if(curr_stats.number_of_windows != 1 || view == curr_view)
2926 {
2927 refresh_view_win(view);
2928 }
2929 }
2930
2931 int
window_shows_dirlist(const view_t * view)2932 window_shows_dirlist(const view_t *view)
2933 {
2934 if(curr_stats.number_of_windows == 1 && view == other_view)
2935 {
2936 return 0;
2937 }
2938
2939 if(view->explore_mode)
2940 {
2941 return 0;
2942 }
2943
2944 if(view == other_view && vle_mode_is(VIEW_MODE))
2945 {
2946 return 0;
2947 }
2948
2949 if(view == other_view && curr_stats.preview.on)
2950 {
2951 return 0;
2952 }
2953
2954 if(NONE(vle_primary_mode_is, NORMAL_MODE, VISUAL_MODE, VIEW_MODE,
2955 CMDLINE_MODE))
2956 {
2957 return 0;
2958 }
2959
2960 return 1;
2961 }
2962
2963 void
change_sort_type(view_t * view,char type,char descending)2964 change_sort_type(view_t *view, char type, char descending)
2965 {
2966 if(cv_compare(view->custom.type))
2967 {
2968 return;
2969 }
2970
2971 view->sort[0] = descending ? -type : type;
2972 memset(&view->sort[1], SK_NONE, sizeof(view->sort) - 1);
2973 memcpy(&view->sort_g[0], &view->sort[0], sizeof(view->sort_g));
2974
2975 fview_sorting_updated(view);
2976
2977 load_sort_option(view);
2978
2979 ui_view_schedule_reload(view);
2980 }
2981
2982 int
pane_in_dir(const view_t * view,const char path[])2983 pane_in_dir(const view_t *view, const char path[])
2984 {
2985 return paths_are_same(view->curr_dir, path);
2986 }
2987
2988 int
cd(view_t * view,const char base_dir[],const char path[])2989 cd(view_t *view, const char base_dir[], const char path[])
2990 {
2991 char dir[PATH_MAX + 1];
2992 char canonic_dir[PATH_MAX + 1];
2993 int updir;
2994
2995 flist_pick_cd_path(view, base_dir, path, &updir, dir, sizeof(dir));
2996 to_canonic_path(dir, base_dir, canonic_dir, sizeof(canonic_dir));
2997
2998 if(updir)
2999 {
3000 rn_leave(view, 1);
3001 }
3002 else if(!cd_is_possible(canonic_dir) ||
3003 change_directory(view, canonic_dir) < 0)
3004 {
3005 return 0;
3006 }
3007
3008 load_dir_list(view, 0);
3009 if(view == curr_view)
3010 {
3011 fview_cursor_redraw(view);
3012 }
3013 else
3014 {
3015 draw_dir_list(other_view);
3016 refresh_view_win(other_view);
3017 }
3018 return 0;
3019 }
3020
3021 void
flist_pick_cd_path(view_t * view,const char base_dir[],const char path[],int * updir,char buf[],size_t buf_size)3022 flist_pick_cd_path(view_t *view, const char base_dir[], const char path[],
3023 int *updir, char buf[], size_t buf_size)
3024 {
3025 char *arg;
3026
3027 *updir = 0;
3028
3029 if(is_null_or_empty(path))
3030 {
3031 copy_str(buf, buf_size, cfg.home_dir);
3032 return;
3033 }
3034
3035 arg = expand_tilde(path);
3036
3037 #ifndef _WIN32
3038 if(is_path_absolute(arg))
3039 copy_str(buf, buf_size, arg);
3040 #else
3041 if(is_path_absolute(arg) && *arg != '/')
3042 copy_str(buf, buf_size, arg);
3043 else if(*arg == '/' && is_unc_root(arg))
3044 copy_str(buf, buf_size, arg);
3045 else if(*arg == '/' && is_unc_path(arg))
3046 copy_str(buf, buf_size, arg);
3047 else if(*arg == '/' && is_unc_path(base_dir))
3048 sprintf(buf + strlen(buf), "/%s", arg + 1);
3049 else if(strcmp(arg, "/") == 0 && is_unc_path(base_dir))
3050 copy_str(buf, strchr(base_dir + 2, '/') - base_dir + 1, base_dir);
3051 else if(*arg == '/')
3052 snprintf(buf, buf_size, "%c:%s", base_dir[0], arg);
3053 #endif
3054 else if(strcmp(arg, "-") == 0)
3055 copy_str(buf, buf_size, view->last_dir == NULL ? "." : view->last_dir);
3056 else if(is_parent_dir(arg) && stroscmp(base_dir, flist_get_dir(view)) == 0)
3057 *updir = 1;
3058 else
3059 find_dir_in_cdpath(base_dir, arg, buf, buf_size);
3060 free(arg);
3061 }
3062
3063 /* Searches for an existing directory in cdpath and fills the buf with final
3064 * absolute path. */
3065 static void
find_dir_in_cdpath(const char base_dir[],const char dst[],char buf[],size_t buf_size)3066 find_dir_in_cdpath(const char base_dir[], const char dst[], char buf[],
3067 size_t buf_size)
3068 {
3069 char *free_this;
3070 char *part, *state;
3071
3072 if(is_builtin_dir(dst) || starts_with_lit(dst, "./") ||
3073 starts_with_lit(dst, "../"))
3074 {
3075 snprintf(buf, buf_size, "%s/%s", base_dir, dst);
3076 return;
3077 }
3078
3079 part = strdup(cfg.cd_path);
3080 free_this = part;
3081
3082 state = NULL;
3083 while((part = split_and_get(part, ',', &state)) != NULL)
3084 {
3085 snprintf(buf, buf_size, "%s/%s", expand_tilde(part), dst);
3086
3087 if(is_dir(buf))
3088 {
3089 free(free_this);
3090 return;
3091 }
3092 }
3093 free(free_this);
3094
3095 snprintf(buf, buf_size, "%s/%s", base_dir, dst);
3096 }
3097
3098 int
go_to_sibling_dir(view_t * view,int offset,int wrap)3099 go_to_sibling_dir(view_t *view, int offset, int wrap)
3100 {
3101 entries_t parent_dirs;
3102 dir_entry_t *entry;
3103 int save_msg = 0;
3104
3105 if(is_root_dir(flist_get_dir(view)) || flist_custom_active(view))
3106 {
3107 return 0;
3108 }
3109
3110 parent_dirs = list_sibling_dirs(view);
3111 if(parent_dirs.nentries < 0)
3112 {
3113 show_error_msg("Error", "Can't list parent directory");
3114 return 0;
3115 }
3116
3117 sort_entries(view, parent_dirs);
3118
3119 entry = pick_sibling(view, parent_dirs, offset, wrap, &save_msg);
3120 if(entry != NULL)
3121 {
3122 char full_path[PATH_MAX + 1];
3123 get_full_path_of(entry, sizeof(full_path), full_path);
3124 if(change_directory(view, full_path) >= 0)
3125 {
3126 load_dir_list(view, 0);
3127 fview_position_updated(view);
3128 }
3129 }
3130
3131 free_dir_entries(view, &parent_dirs.entries, &parent_dirs.nentries);
3132 return save_msg;
3133 }
3134
3135 /* Lists siblings of current directory of the view (includes current directory
3136 * as well). Returns the list, which is of length -1 on error. */
3137 static entries_t
list_sibling_dirs(view_t * view)3138 list_sibling_dirs(view_t *view)
3139 {
3140 entries_t parent_dirs = {};
3141 char *path;
3142
3143 if(is_root_dir(flist_get_dir(view)))
3144 {
3145 parent_dirs.nentries = -1;
3146 return parent_dirs;
3147 }
3148
3149 path = strdup(flist_get_dir(view));
3150 remove_last_path_component(path);
3151
3152 parent_dirs = flist_list_in(view, path, 1, 0);
3153
3154 free(path);
3155
3156 if(parent_dirs.nentries < 0)
3157 {
3158 return parent_dirs;
3159 }
3160
3161 if(entry_from_path(view, parent_dirs.entries, parent_dirs.nentries,
3162 flist_get_dir(view)) == NULL)
3163 {
3164 /* If we couldn't find our current directory in the list (because it got
3165 * filtered-out), add it to be able to determine where it would go if it
3166 * were visible. */
3167 entry_list_add(view, &parent_dirs.entries, &parent_dirs.nentries,
3168 flist_get_dir(view));
3169 }
3170
3171 return parent_dirs;
3172 }
3173
3174 /* Lists files of specified directory. Returns the list, which is of length -1
3175 * on error. */
3176 static entries_t
flist_list_in(view_t * view,const char path[],int only_dirs,int can_include_parent)3177 flist_list_in(view_t *view, const char path[], int only_dirs,
3178 int can_include_parent)
3179 {
3180 entries_t siblings = {};
3181 int len, i;
3182 char **list;
3183
3184 list = list_all_files(path, &len);
3185 if(len < 0)
3186 {
3187 siblings.nentries = -1;
3188 return siblings;
3189 }
3190
3191 for(i = 0; i < len; ++i)
3192 {
3193 dir_entry_t *entry;
3194 int is_dir;
3195
3196 if(view->hide_dot && list[i][0] == '.')
3197 {
3198 continue;
3199 }
3200
3201 char *full_path = format_str("%s/%s", path, list[i]);
3202 entry = entry_list_add(view, &siblings.entries, &siblings.nentries,
3203 full_path);
3204 free(full_path);
3205
3206 if(entry == NULL)
3207 {
3208 continue;
3209 }
3210
3211 is_dir = fentry_is_dir(entry);
3212 if((only_dirs && !is_dir) ||
3213 !filters_file_is_visible(view, path, list[i], is_dir, 0))
3214 {
3215 fentry_free(view, entry);
3216 --siblings.nentries;
3217 }
3218 }
3219 free_string_array(list, len);
3220
3221 if(can_include_parent && cfg_parent_dir_is_visible(is_root_dir(path)))
3222 {
3223 char *const full_path = format_str("%s/..", path);
3224 entry_list_add(view, &siblings.entries, &siblings.nentries, full_path);
3225 free(full_path);
3226 }
3227
3228 return siblings;
3229 }
3230
3231 /* Picks next or previous sibling from the list with optional wrapping.
3232 * *wrapped is set to non-zero if wrapping happened. Returns pointer to picked
3233 * sibling or NULL on error or if can't pick. */
3234 static dir_entry_t *
pick_sibling(view_t * view,entries_t parent_dirs,int offset,int wrap,int * wrapped)3235 pick_sibling(view_t *view, entries_t parent_dirs, int offset, int wrap,
3236 int *wrapped)
3237 {
3238 dir_entry_t *entry;
3239 int i, initial;
3240
3241 /* Find parent entry. */
3242 entry = entry_from_path(view, parent_dirs.entries, parent_dirs.nentries,
3243 flist_get_dir(view));
3244 if(entry == NULL)
3245 {
3246 show_error_msg("Error", "Can't find current directory position");
3247 return NULL;
3248 }
3249
3250 *wrapped = 0;
3251 initial = entry - parent_dirs.entries;
3252 i = initial;
3253
3254 while(offset != 0)
3255 {
3256 if(offset > 0 && i < parent_dirs.nentries - 1)
3257 {
3258 ++i;
3259 }
3260 else if(offset < 0 && i > 0)
3261 {
3262 --i;
3263 }
3264 else if(wrap)
3265 {
3266 if(offset > 0)
3267 {
3268 i = 0;
3269 ui_sb_err("Hit BOTTOM, continuing at TOP");
3270 }
3271 else
3272 {
3273 i = parent_dirs.nentries - 1;
3274 ui_sb_err("Hit TOP, continuing at BOTTOM");
3275 }
3276 *wrapped = 1;
3277 }
3278 else
3279 {
3280 return (i == initial ? NULL : entry);
3281 }
3282
3283 entry = &parent_dirs.entries[i];
3284 offset = (offset > 0 ? offset - 1 : offset + 1);
3285 }
3286
3287 return entry;
3288 }
3289
3290 int
view_needs_cd(const view_t * view,const char path[])3291 view_needs_cd(const view_t *view, const char path[])
3292 {
3293 if(path[0] == '\0' || stroscmp(view->curr_dir, path) == 0)
3294 {
3295 return 0;
3296 }
3297 return 1;
3298 }
3299
3300 void
set_view_path(view_t * view,const char path[])3301 set_view_path(view_t *view, const char path[])
3302 {
3303 if(view_needs_cd(view, path))
3304 {
3305 copy_str(view->curr_dir, sizeof(view->curr_dir), path);
3306 exclude_file_name(view->curr_dir);
3307 }
3308 }
3309
3310 uint64_t
fentry_get_size(const view_t * view,const dir_entry_t * entry)3311 fentry_get_size(const view_t *view, const dir_entry_t *entry)
3312 {
3313 uint64_t size = DCACHE_UNKNOWN;
3314
3315 if(fentry_is_dir(entry))
3316 {
3317 fentry_get_dir_info(view, entry, &size, NULL);
3318 }
3319
3320 return (size == DCACHE_UNKNOWN ? entry->size : size);
3321 }
3322
3323 int
iter_selected_entries(view_t * view,dir_entry_t ** entry)3324 iter_selected_entries(view_t *view, dir_entry_t **entry)
3325 {
3326 return iter_entries(view, entry, &is_entry_selected);
3327 }
3328
3329 int
iter_marked_entries(view_t * view,dir_entry_t ** entry)3330 iter_marked_entries(view_t *view, dir_entry_t **entry)
3331 {
3332 return iter_entries(view, entry, &is_entry_marked);
3333 }
3334
3335 /* Implements iteration over the entries which match specific predicate.
3336 * Returns non-zero if matching entry is found and is loaded to *entry,
3337 * otherwise it's set to NULL and zero is returned. */
3338 static int
iter_entries(view_t * view,dir_entry_t ** entry,entry_predicate pred)3339 iter_entries(view_t *view, dir_entry_t **entry, entry_predicate pred)
3340 {
3341 int next = (*entry == NULL) ? 0 : (*entry - view->dir_entry + 1);
3342
3343 while(next < view->list_rows)
3344 {
3345 dir_entry_t *const e = &view->dir_entry[next];
3346 if(fentry_is_valid(e) && pred(e))
3347 {
3348 *entry = e;
3349 return 1;
3350 }
3351 ++next;
3352 }
3353
3354 *entry = NULL;
3355 return 0;
3356 }
3357
3358 int
is_entry_selected(const dir_entry_t * entry)3359 is_entry_selected(const dir_entry_t *entry)
3360 {
3361 return entry->selected;
3362 }
3363
3364 int
is_entry_marked(const dir_entry_t * entry)3365 is_entry_marked(const dir_entry_t *entry)
3366 {
3367 return entry->marked;
3368 }
3369
3370 int
iter_selection_or_current(view_t * view,dir_entry_t ** entry)3371 iter_selection_or_current(view_t *view, dir_entry_t **entry)
3372 {
3373 if(view->selected_files == 0)
3374 {
3375 dir_entry_t *const curr = get_current_entry(view);
3376 *entry = (*entry == NULL && fentry_is_valid(curr)) ? curr : NULL;
3377 return *entry != NULL;
3378 }
3379 return iter_selected_entries(view, entry);
3380 }
3381
3382 int
entry_to_pos(const view_t * view,const dir_entry_t * entry)3383 entry_to_pos(const view_t *view, const dir_entry_t *entry)
3384 {
3385 const int pos = entry - view->dir_entry;
3386 return (pos >= 0 && pos < view->list_rows) ? pos : -1;
3387 }
3388
3389 void
get_current_full_path(const view_t * view,size_t buf_len,char buf[])3390 get_current_full_path(const view_t *view, size_t buf_len, char buf[])
3391 {
3392 get_full_path_at(view, view->list_pos, buf_len, buf);
3393 }
3394
3395 void
get_full_path_at(const view_t * view,int pos,size_t buf_len,char buf[])3396 get_full_path_at(const view_t *view, int pos, size_t buf_len, char buf[])
3397 {
3398 if(pos < 0 || pos >= view->list_rows)
3399 {
3400 copy_str(buf, buf_len, "");
3401 return;
3402 }
3403 get_full_path_of(&view->dir_entry[pos], buf_len, buf);
3404 }
3405
3406 void
get_full_path_of(const dir_entry_t * entry,size_t buf_len,char buf[])3407 get_full_path_of(const dir_entry_t *entry, size_t buf_len, char buf[])
3408 {
3409 build_path(buf, buf_len, entry->origin, entry->name);
3410 }
3411
3412 void
get_short_path_of(const view_t * view,const dir_entry_t * entry,NameFormat fmt,int drop_prefix,size_t buf_len,char buf[])3413 get_short_path_of(const view_t *view, const dir_entry_t *entry, NameFormat fmt,
3414 int drop_prefix, size_t buf_len, char buf[])
3415 {
3416 char name[NAME_MAX + 1];
3417 const char *path = entry->origin;
3418
3419 char parent_path[PATH_MAX + 1];
3420 const char *root_path = flist_get_dir(view);
3421
3422 if(fentry_is_fake(entry))
3423 {
3424 copy_str(buf, buf_len, "");
3425 return;
3426 }
3427
3428 if(drop_prefix && entry->child_pos != 0)
3429 {
3430 /* Replace root to force obtaining of file name only. */
3431 const dir_entry_t *const parent_entry = entry - entry->child_pos;
3432 get_full_path_of(parent_entry, sizeof(parent_path), parent_path);
3433 root_path = parent_path;
3434 }
3435
3436 format_entry_name(entry, fmt, sizeof(name), name);
3437
3438 if(is_parent_dir(entry->name) || is_root_dir(entry->name))
3439 {
3440 copy_str(buf, buf_len, name);
3441 return;
3442 }
3443
3444 if(!path_starts_with(path, root_path))
3445 {
3446 build_path(buf, buf_len, path, name);
3447 return;
3448 }
3449
3450 assert(strlen(path) >= strlen(root_path) && "Path is too short.");
3451
3452 path += strlen(root_path);
3453 path = skip_char(path, '/');
3454 if(path[0] == '\0')
3455 {
3456 copy_str(buf, buf_len, name);
3457 }
3458 else
3459 {
3460 snprintf(buf, buf_len, "%s/%s", path, name);
3461 }
3462 }
3463
3464 void
check_marking(view_t * view,int count,const int indexes[])3465 check_marking(view_t *view, int count, const int indexes[])
3466 {
3467 if(count != 0)
3468 {
3469 mark_files_at(view, count, indexes);
3470 }
3471 else
3472 {
3473 flist_set_marking(view, 0);
3474 }
3475 }
3476
3477 void
flist_set_marking(view_t * view,int prefer_current)3478 flist_set_marking(view_t *view, int prefer_current)
3479 {
3480 if(view->pending_marking)
3481 {
3482 view->pending_marking = 0;
3483 return;
3484 }
3485
3486 dir_entry_t *curr = get_current_entry(view);
3487 if(view->selected_files != 0 &&
3488 (!prefer_current || (curr != NULL && curr->selected)))
3489 {
3490 mark_selected(view);
3491 }
3492 else
3493 {
3494 clear_marking(view);
3495
3496 if(curr != NULL && fentry_is_valid(curr))
3497 {
3498 curr->marked = 1;
3499 }
3500 }
3501 }
3502
3503 void
mark_files_at(view_t * view,int count,const int indexes[])3504 mark_files_at(view_t *view, int count, const int indexes[])
3505 {
3506 int i;
3507
3508 clear_marking(view);
3509
3510 for(i = 0; i < count; ++i)
3511 {
3512 view->dir_entry[indexes[i]].marked = 1;
3513 }
3514 }
3515
3516 /* Marks selected files of the view. Returns number of marked files. */
3517 static int
mark_selected(view_t * view)3518 mark_selected(view_t *view)
3519 {
3520 int i;
3521 int nmarked = 0;
3522 for(i = 0; i < view->list_rows; ++i)
3523 {
3524 view->dir_entry[i].marked = view->dir_entry[i].selected;
3525 if(view->dir_entry[i].marked)
3526 {
3527 ++nmarked;
3528 }
3529 }
3530 return nmarked;
3531 }
3532
3533 int
mark_selection_or_current(view_t * view)3534 mark_selection_or_current(view_t *view)
3535 {
3536 dir_entry_t *const curr = get_current_entry(view);
3537 if(view->selected_files == 0 && fentry_is_valid(curr))
3538 {
3539 clear_marking(view);
3540 curr->marked = 1;
3541 return 1;
3542 }
3543 return mark_selected(view);
3544 }
3545
3546 int
flist_count_marked(const view_t * view)3547 flist_count_marked(const view_t *view)
3548 {
3549 int i, n = 0;
3550 for(i = 0; i < view->list_rows; ++i)
3551 {
3552 n += (view->dir_entry[i].marked != 0);
3553 }
3554 return n;
3555 }
3556
3557 void
clear_marking(view_t * view)3558 clear_marking(view_t *view)
3559 {
3560 int i;
3561 for(i = 0; i < view->list_rows; ++i)
3562 {
3563 view->dir_entry[i].marked = 0;
3564 }
3565 }
3566
3567 void
flist_custom_set(view_t * view,const char title[],const char path[],char * lines[],int nlines)3568 flist_custom_set(view_t *view, const char title[], const char path[],
3569 char *lines[], int nlines)
3570 {
3571 int i;
3572
3573 if(vifm_chdir(path) != 0)
3574 {
3575 show_error_msgf("Custom view", "Can't change directory: %s", path);
3576 return;
3577 }
3578
3579 flist_custom_start(view, "-");
3580
3581 for(i = 0; i < nlines; ++i)
3582 {
3583 flist_custom_add_spec(view, lines[i]);
3584 }
3585
3586 flist_custom_end(view, 1);
3587 }
3588
3589 void
flist_custom_add_spec(view_t * view,const char line[])3590 flist_custom_add_spec(view_t *view, const char line[])
3591 {
3592 char *const path = parse_line_for_path(line, flist_get_dir(view));
3593 if(path != NULL)
3594 {
3595 flist_custom_add(view, path);
3596 free(path);
3597 }
3598 }
3599
3600 void
flist_custom_end(view_t * view,int very)3601 flist_custom_end(view_t *view, int very)
3602 {
3603 if(flist_custom_finish(view, very ? CV_VERY : CV_REGULAR, 0) != 0)
3604 {
3605 show_error_msg("Custom view", "Ignoring empty list of files");
3606 return;
3607 }
3608
3609 fpos_set_pos(view, 0);
3610 }
3611
3612 void
fentry_rename(view_t * view,dir_entry_t * entry,const char to[])3613 fentry_rename(view_t *view, dir_entry_t *entry, const char to[])
3614 {
3615 char *const old_name = entry->name;
3616
3617 /* Rename file in internal structures for correct positioning of cursor
3618 * after reloading, as cursor will be positioned on the file with the same
3619 * name. */
3620 entry->name = strdup(to);
3621 if(entry->name == NULL)
3622 {
3623 entry->name = old_name;
3624 return;
3625 }
3626
3627 /* Name change can affect name specific highlight and decorations, so reset
3628 * the caches. */
3629 entry->hi_num = -1;
3630 entry->name_dec_num = -1;
3631
3632 /* Update origins of entries which include the one we're renaming. */
3633 if(flist_custom_active(view) && fentry_is_dir(entry))
3634 {
3635 int i;
3636 char *const root = format_str("%s/%s", entry->origin, old_name);
3637 const size_t root_len = strlen(root);
3638
3639 /* TODO: this doesn't handle circular renames like a/ -> b/, needs more
3640 * bookkeeping. */
3641 for(i = 0; i < view->list_rows; ++i)
3642 {
3643 dir_entry_t *const e = &view->dir_entry[i];
3644 if(path_starts_with(e->origin, root))
3645 {
3646 char *const new_origin = format_str("%s/%s%s", entry->origin, to,
3647 e->origin + root_len);
3648 chosp(new_origin);
3649 if(e->origin != view->curr_dir)
3650 {
3651 free(e->origin);
3652 }
3653 e->origin = new_origin;
3654 }
3655 }
3656
3657 free(root);
3658 }
3659
3660 free(old_name);
3661 }
3662
3663 int
fentry_is_fake(const dir_entry_t * entry)3664 fentry_is_fake(const dir_entry_t *entry)
3665 {
3666 return entry->name[0] == '\0';
3667 }
3668
3669 int
fentry_is_valid(const dir_entry_t * entry)3670 fentry_is_valid(const dir_entry_t *entry)
3671 {
3672 return !fentry_is_fake(entry) && !is_parent_dir(entry->name);
3673 }
3674
3675 int
fentry_is_dir(const dir_entry_t * entry)3676 fentry_is_dir(const dir_entry_t *entry)
3677 {
3678 return (entry->type == FT_LINK) ? entry->dir_link : (entry->type == FT_DIR);
3679 }
3680
3681 int
flist_load_tree(view_t * view,const char path[])3682 flist_load_tree(view_t *view, const char path[])
3683 {
3684 char full_path[PATH_MAX + 1];
3685 get_current_full_path(view, sizeof(full_path), full_path);
3686
3687 if(flist_load_tree_internal(view, path, 0) != 0)
3688 {
3689 return 1;
3690 }
3691
3692 if(full_path[0] != '\0')
3693 {
3694 (void)set_position_by_path(view, full_path);
3695 }
3696
3697 ui_view_schedule_redraw(view);
3698 return 0;
3699 }
3700
3701 /* Looks up entry by its path in main entry list of the view and updates cursor
3702 * position when such entry is found. Returns zero if position was updated,
3703 * otherwise non-zero is returned. */
3704 static int
set_position_by_path(view_t * view,const char path[])3705 set_position_by_path(view_t *view, const char path[])
3706 {
3707 const dir_entry_t *entry;
3708 entry = entry_from_path(view, view->dir_entry, view->list_rows, path);
3709 if(entry != NULL)
3710 {
3711 view->list_pos = entry_to_pos(view, entry);
3712 return 0;
3713 }
3714 return 1;
3715 }
3716
3717 int
flist_clone_tree(view_t * to,const view_t * from)3718 flist_clone_tree(view_t *to, const view_t *from)
3719 {
3720 if(from->custom.type == CV_CUSTOM_TREE)
3721 {
3722 const int as_tree = (from->custom.type == CV_CUSTOM_TREE);
3723 flist_custom_clone(to, from, as_tree);
3724 }
3725 else
3726 {
3727 if(make_tree(to, flist_get_dir(from), 0, from->custom.excluded_paths) != 0)
3728 {
3729 return 1;
3730 }
3731 }
3732
3733 trie_free(to->custom.excluded_paths);
3734 to->custom.excluded_paths = trie_clone(from->custom.excluded_paths);
3735 return 0;
3736 }
3737
3738 /* Implements tree view (re)loading. Returns zero on success, otherwise
3739 * non-zero is returned. */
3740 static int
flist_load_tree_internal(view_t * view,const char path[],int reload)3741 flist_load_tree_internal(view_t *view, const char path[], int reload)
3742 {
3743 trie_t *excluded_paths = reload ? view->custom.excluded_paths : NULL;
3744
3745 if(make_tree(view, path, reload, excluded_paths) != 0)
3746 {
3747 return 1;
3748 }
3749
3750 if(!reload)
3751 {
3752 trie_free(view->custom.excluded_paths);
3753 view->custom.excluded_paths = trie_create();
3754 }
3755 return 0;
3756 }
3757
3758 /* (Re)loads tree at path into the view using specified list of excluded files.
3759 * Returns zero on success, otherwise non-zero is returned. */
3760 static int
make_tree(view_t * view,const char path[],int reload,trie_t * excluded_paths)3761 make_tree(view_t *view, const char path[], int reload, trie_t *excluded_paths)
3762 {
3763 char canonic_path[PATH_MAX + 1];
3764 int nfiltered;
3765 CVType type;
3766 const int from_custom = flist_custom_active(view)
3767 && ONE_OF(view->custom.type, CV_REGULAR, CV_VERY);
3768
3769 flist_custom_start(view, from_custom ? view->custom.title : "");
3770
3771 show_progress("Building tree...", 0);
3772
3773 ui_cancellation_push_on();
3774 if(from_custom)
3775 {
3776 nfiltered = 0;
3777 tree_from_cv(view);
3778 type = CV_CUSTOM_TREE;
3779 }
3780 else
3781 {
3782 nfiltered = add_files_recursively(view, path, excluded_paths, -1, 0);
3783 type = CV_TREE;
3784 }
3785 ui_cancellation_pop();
3786
3787 ui_sb_quick_msg_clear();
3788
3789 if(ui_cancellation_requested())
3790 {
3791 return 1;
3792 }
3793
3794 if(nfiltered < 0)
3795 {
3796 show_error_msg("Tree View", "Failed to list directory");
3797 return 1;
3798 }
3799
3800 to_canonic_path(path, flist_get_dir(view), canonic_path,
3801 sizeof(canonic_path));
3802
3803 if(flist_custom_finish_internal(view, type, reload, canonic_path, 1) != 0)
3804 {
3805 return 1;
3806 }
3807 view->filtered = nfiltered;
3808
3809 replace_string(&view->custom.orig_dir, canonic_path);
3810
3811 return 0;
3812 }
3813
3814 /* Turns custom list into custom tree. */
3815 static void
tree_from_cv(view_t * view)3816 tree_from_cv(view_t *view)
3817 {
3818 int i;
3819
3820 dir_entry_t *entries = view->dir_entry;
3821 int nentries = view->list_rows;
3822
3823 fsdata_t *const tree = fsdata_create(0, 0);
3824
3825 view->dir_entry = NULL;
3826 view->list_rows = 0;
3827
3828 for(i = 0U; i < nentries; ++i)
3829 {
3830 if(!is_parent_dir(entries[i].name))
3831 {
3832 char full_path[PATH_MAX + 1];
3833 void *data = &entries[i];
3834 get_full_path_of(&entries[i], sizeof(full_path), full_path);
3835 fsdata_set(tree, full_path, &data, sizeof(data));
3836
3837 /* Mark entries that came from original list. */
3838 entries[i].tag = 1;
3839 }
3840 else
3841 {
3842 fentry_free(view, &entries[i]);
3843 }
3844 }
3845
3846 if(fsdata_traverse(tree, &complete_tree, view) != 0)
3847 {
3848 reset_entry_list(view, &view->custom.entries, &view->custom.entry_count);
3849 }
3850
3851 fsdata_free(tree);
3852 dynarray_free(entries);
3853
3854 drop_tops(view, view->custom.entries, &view->custom.entry_count, 1);
3855 }
3856
3857 /* fsdata_traverse() callback that flattens the tree into array of entries.
3858 * Should return non-zero to stop traverser. */
3859 static int
complete_tree(const char name[],int valid,const void * parent_data,void * data,void * arg)3860 complete_tree(const char name[], int valid, const void *parent_data, void *data,
3861 void *arg)
3862 {
3863 /* Initially data associated with existing entries points to original entries.
3864 * Once that entry is copied, the data is replaced with its index in the new
3865 * list. */
3866
3867 view_t *const view = arg;
3868 const int in_place = cv_tree(view->custom.type);
3869
3870 dir_entry_t **entries = (in_place ? &view->dir_entry : &view->custom.entries);
3871 int *nentries = (in_place ? &view->list_rows : &view->custom.entry_count);
3872
3873 dir_entry_t *dir_entry = alloc_dir_entry(entries, *nentries);
3874 if(dir_entry == NULL)
3875 {
3876 return 1;
3877 }
3878
3879 ++*nentries;
3880
3881 if(valid)
3882 {
3883 *dir_entry = **(dir_entry_t **)data;
3884 dir_entry->child_count = 0;
3885 (*(dir_entry_t **)data)->name = NULL;
3886 (*(dir_entry_t **)data)->origin = NULL;
3887 }
3888 else
3889 {
3890 char full_path[PATH_MAX + 1];
3891
3892 if(parent_data == NULL)
3893 {
3894 char *const typed_path = format_str("%s/", name);
3895 if(is_root_dir(typed_path))
3896 {
3897 /* Entry for a root is added temporarily (this happens only on Windows)
3898 * as a storage of path prefix and is removed afterwards in
3899 * drop_tops(). */
3900 init_dir_entry(view, dir_entry, "");
3901 dir_entry->origin = strdup(name);
3902 }
3903 else
3904 {
3905 init_dir_entry(view, dir_entry, name);
3906 dir_entry->origin = strdup("/");
3907 }
3908 free(typed_path);
3909 }
3910 else
3911 {
3912 char parent_path[PATH_MAX + 1];
3913 const intptr_t *parent_idx = parent_data;
3914 init_dir_entry(view, dir_entry, name);
3915 get_full_path_of(&(*entries)[*parent_idx], sizeof(parent_path),
3916 parent_path);
3917 dir_entry->origin = strdup(parent_path);
3918 }
3919
3920 get_full_path_of(dir_entry, sizeof(full_path), full_path);
3921 fill_dir_entry_by_path(dir_entry, full_path);
3922
3923 dir_entry->temporary = in_place;
3924 }
3925
3926 *(intptr_t *)data = dir_entry - *entries;
3927
3928 if(parent_data != NULL)
3929 {
3930 const intptr_t *const parent_idx = parent_data;
3931 dir_entry->child_pos = (dir_entry - *entries) - *parent_idx;
3932
3933 do
3934 {
3935 dir_entry -= dir_entry->child_pos;
3936 ++dir_entry->child_count;
3937 }
3938 while(dir_entry->child_pos != 0);
3939 }
3940 return 0;
3941 }
3942
3943 /* Replaces list of entries with a single parent directory entry. Might leave
3944 * the list empty on memory error. */
3945 static void
reset_entry_list(view_t * view,dir_entry_t ** entries,int * count)3946 reset_entry_list(view_t *view, dir_entry_t **entries, int *count)
3947 {
3948 free_dir_entries(view, entries, count);
3949 add_parent_entry(view, entries, count);
3950 }
3951
3952 /* Traverses root children and drops fake root nodes and optionally extra tops
3953 * of subtrees. */
3954 static void
drop_tops(view_t * view,dir_entry_t * entries,int * nentries,int extra)3955 drop_tops(view_t *view, dir_entry_t *entries, int *nentries, int extra)
3956 {
3957 int i;
3958 for(i = 0; i < *nentries - 1; i += entries[i].child_count + 1)
3959 {
3960 int j = i;
3961 while(j < *nentries - 1 && entries[j].child_count > 0 &&
3962 ((extra && entries[j].child_count == entries[j + 1].child_count + 1) ||
3963 entries[j].name[0] == '\0') && entries[j].tag != 1)
3964 {
3965 fentry_free(view, &entries[j++]);
3966 }
3967
3968 memmove(&entries[i], &entries[j], sizeof(*entries)*(*nentries - j));
3969 *nentries -= j - i;
3970 entries[i].child_pos = 0;
3971 }
3972 }
3973
3974 /* Adds custom view entries corresponding to file system tree. parent_pos is
3975 * expected to be negative for the outermost invocation. Returns number of
3976 * filtered out files on success or partial success and negative value on
3977 * serious error. */
3978 static int
add_files_recursively(view_t * view,const char path[],trie_t * excluded_paths,int parent_pos,int no_direct_parent)3979 add_files_recursively(view_t *view, const char path[], trie_t *excluded_paths,
3980 int parent_pos, int no_direct_parent)
3981 {
3982 int i;
3983 const int prev_count = view->custom.entry_count;
3984 int nfiltered = 0;
3985
3986 int len;
3987 char **lst = list_all_files(path, &len);
3988 if(len < 0)
3989 {
3990 return -1;
3991 }
3992
3993 for(i = 0; i < len && !ui_cancellation_requested(); ++i)
3994 {
3995 int dir;
3996 void *dummy;
3997 dir_entry_t *entry;
3998 char *const full_path = format_str("%s/%s", path, lst[i]);
3999
4000 if(trie_get(excluded_paths, full_path, &dummy) == 0)
4001 {
4002 free(full_path);
4003 continue;
4004 }
4005
4006 dir = is_dir(full_path);
4007 if(!file_is_visible(view, lst[i], dir, NULL, 1))
4008 {
4009 /* Traverse directory (but not symlink to it) even if we're skipping it,
4010 * because we might need files that are inside of it. */
4011 if(dir && !is_symlink(full_path) &&
4012 file_is_visible(view, lst[i], dir, NULL, 0))
4013 {
4014 nfiltered += add_files_recursively(view, full_path, excluded_paths,
4015 parent_pos, 1);
4016 }
4017
4018 free(full_path);
4019 ++nfiltered;
4020 continue;
4021 }
4022
4023 entry = flist_custom_add(view, full_path);
4024 if(entry == NULL)
4025 {
4026 free(full_path);
4027 free_string_array(lst, len);
4028 return -1;
4029 }
4030
4031 if(parent_pos >= 0)
4032 {
4033 entry->child_pos = (view->custom.entry_count - 1) - parent_pos;
4034 }
4035
4036 /* Not using dir variable here, because it is set for symlinks to
4037 * directories as well. */
4038 if(entry->type == FT_DIR)
4039 {
4040 const int idx = view->custom.entry_count - 1;
4041 const int filtered = add_files_recursively(view, full_path,
4042 excluded_paths, idx, 0);
4043 /* Keep going in case of error and load partial list. */
4044 if(filtered >= 0)
4045 {
4046 /* If one of recursive calls returned error, keep going and build
4047 * partial tree. */
4048 view->custom.entries[idx].child_count = (view->custom.entry_count - 1)
4049 - idx;
4050 nfiltered += filtered;
4051 }
4052 }
4053
4054 free(full_path);
4055
4056 show_progress("Building tree...", 1000);
4057 }
4058
4059 free_string_array(lst, len);
4060
4061 /* The prev_count != 0 check is to make sure that we won't create leaf instead
4062 * of the whole tree (this is handled in flist_custom_finish()). */
4063 int show_empty_dir_leafs = (cfg.dot_dirs & DD_TREE_LEAFS_PARENT);
4064 if(show_empty_dir_leafs && !no_direct_parent && prev_count != 0 &&
4065 view->custom.entry_count == prev_count)
4066 {
4067 /* To be able to perform operations inside directory (e.g., create files),
4068 * we need at least one element there. */
4069 if(add_directory_leaf(view, path, parent_pos) != 0)
4070 {
4071 return -1;
4072 }
4073 }
4074
4075 return nfiltered;
4076 }
4077
4078 /* Checks whether file is visible according to dot and filename filters. is_dir
4079 * is used when data is NULL, otherwise data_is_dir_entry() called (this is an
4080 * optimization). Returns non-zero if so, otherwise zero is returned. */
4081 static int
file_is_visible(view_t * view,const char name[],int is_dir,const void * data,int apply_local_filter)4082 file_is_visible(view_t *view, const char name[], int is_dir, const void *data,
4083 int apply_local_filter)
4084 {
4085 if(view->hide_dot && name[0] == '.')
4086 {
4087 return 0;
4088 }
4089
4090 if(data != NULL)
4091 {
4092 char full_path[PATH_MAX + 1];
4093 snprintf(full_path, sizeof(full_path), "%s/%s", flist_get_dir(view), name);
4094
4095 is_dir = data_is_dir_entry(data, full_path);
4096 }
4097
4098 return filters_file_is_visible(view, flist_get_dir(view), name, is_dir,
4099 apply_local_filter);
4100 }
4101
4102 /* Adds ".." directory leaf of an empty directory to the tree which is being
4103 * built. Returns non-zero on error, otherwise zero is returned. */
4104 static int
add_directory_leaf(view_t * view,const char path[],int parent_pos)4105 add_directory_leaf(view_t *view, const char path[], int parent_pos)
4106 {
4107 char *full_path;
4108 dir_entry_t *entry;
4109
4110 /* If local filter isn't empty, assume that user is looking for something and
4111 * leafs will get in his way. */
4112 if(!filter_is_empty(&view->local_filter.filter))
4113 {
4114 return 0;
4115 }
4116
4117 entry = alloc_dir_entry(&view->custom.entries, view->custom.entry_count);
4118 if(entry == NULL)
4119 {
4120 show_error_msg("Memory Error", "Unable to allocate enough memory");
4121 return 1;
4122 }
4123
4124 full_path = format_str("%s/..", path);
4125 if(init_parent_entry(view, entry, full_path) != 0)
4126 {
4127 free(full_path);
4128 /* Keep going in case of error and load partial list. */
4129 return 0;
4130 }
4131
4132 remove_last_path_component(full_path);
4133 entry->origin = full_path;
4134
4135 if(parent_pos >= 0)
4136 {
4137 entry->child_pos = (view->custom.entry_count - 1) - parent_pos;
4138 }
4139
4140 ++view->custom.entry_count;
4141 return 0;
4142 }
4143
4144 /* Fills given entry of the view at specified path (can be relative, i.e. "..").
4145 * Returns zero on success, otherwise non-zero is returned. */
4146 static int
init_parent_entry(view_t * view,dir_entry_t * entry,const char path[])4147 init_parent_entry(view_t *view, dir_entry_t *entry, const char path[])
4148 {
4149 struct stat s;
4150
4151 init_dir_entry(view, entry, get_last_path_component(path));
4152 entry->type = FT_DIR;
4153
4154 /* Load the inode info or leave blank values in entry. */
4155 if(os_lstat(path, &s) != 0)
4156 {
4157 fentry_free(view, entry);
4158 LOG_SERROR_MSG(errno, "Can't lstat() \"%s\"", path);
4159 log_cwd();
4160 return 1;
4161 }
4162
4163 #ifndef _WIN32
4164 entry->size = (uintmax_t)s.st_size;
4165 entry->uid = s.st_uid;
4166 entry->gid = s.st_gid;
4167 entry->mode = s.st_mode;
4168 entry->inode = s.st_ino;
4169 #else
4170 /* Windows doesn't like returning size of directories even if it can. */
4171 entry->size = get_file_size(entry->name);
4172 #endif
4173 entry->mtime = s.st_mtime;
4174 entry->atime = s.st_atime;
4175 entry->ctime = s.st_ctime;
4176 entry->nlinks = s.st_nlink;
4177
4178 return 0;
4179 }
4180
4181 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
4182 /* vim: set cinoptions+=t0 filetype=c : */
4183