/* vifm
 * Copyright (C) 2001 Ken Steen.
 * Copyright (C) 2011 xaizek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "filelist.h"

#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#include <lm.h>
#include <winioctl.h>
#endif

#include <curses.h>

#include <sys/stat.h> /* stat */

#include <assert.h> /* assert() */
#include <errno.h> /* errno */
#include <limits.h> /* INT_MAX INT_MIN */
#include <stddef.h> /* NULL size_t */
#include <stdint.h> /* intptr_t uint64_t */
#include <stdio.h> /* snprintf() */
#include <stdlib.h> /* calloc() free() */
#include <string.h> /* memcmp() memcpy() memset() strcat() strcmp() strcpy()
                       strdup() strlen() */

#include "cfg/config.h"
#include "compat/fs_limits.h"
#include "compat/os.h"
#include "compat/pthread.h"
#include "engine/autocmds.h"
#include "engine/mode.h"
#include "int/fuse.h"
#include "modes/dialogs/msg_dialog.h"
#include "modes/modes.h"
#include "modes/view.h"
#include "ui/cancellation.h"
#include "ui/column_view.h"
#include "ui/fileview.h"
#include "ui/statusbar.h"
#include "ui/statusline.h"
#include "ui/tabs.h"
#include "ui/ui.h"
#include "utils/dynarray.h"
#include "utils/env.h"
#include "utils/fs.h"
#include "utils/fsdata.h"
#include "utils/fswatch.h"
#include "utils/log.h"
#include "utils/macros.h"
#include "utils/matcher.h"
#include "utils/path.h"
#include "utils/regexp.h"
#include "utils/str.h"
#include "utils/string_array.h"
#include "utils/test_helpers.h"
#include "utils/trie.h"
#include "utils/utf8.h"
#include "utils/utils.h"
#include "filtering.h"
#include "flist_hist.h"
#include "flist_pos.h"
#include "flist_sel.h"
#include "fops_misc.h"
#include "macros.h"
#include "marks.h"
#include "opt_handlers.h"
#include "registers.h"
#include "running.h"
#include "sort.h"
#include "status.h"
#include "types.h"

/* State of a fold. */
typedef enum
{
	FOLD_UNDEFINED,   /* Indication of undefined state. */
	FOLD_USER_OPENED, /* User manually opened the fold. */
	FOLD_USER_CLOSED, /* User manually closed the fold. */
	FOLD_AUTO_OPENED, /* An auto-closed fold that was opened the first time. */
	FOLD_AUTO_CLOSED, /* Closed on reaching depth limit on building or after
	                     opening such a fold ("lazy unfolding"). */
}
FoldState;

static void init_flist(view_t *view);
static void reset_view(view_t *view);
static void init_view_history(view_t *view);
static int navigate_to_file_in_custom_view(view_t *view, const char dir[],
		const char file[]);
static int fill_dir_entry_by_path(dir_entry_t *entry, const char path[]);
static void on_custom_view_leave(view_t *view);
#ifndef _WIN32
static int fill_dir_entry(dir_entry_t *entry, const char path[],
		const struct dirent *d);
static int data_is_dir_entry(const struct dirent *d, const char path[]);
#else
static int fill_dir_entry(dir_entry_t *entry, const char path[],
		const WIN32_FIND_DATAW *ffd);
static int data_is_dir_entry(const WIN32_FIND_DATAW *ffd, const char path[]);
#endif
static int flist_custom_finish_internal(view_t *view, CVType type, int reload,
		const char dir[], int allow_empty);
static void on_location_change(view_t *view, int force);
static void disable_view_sorting(view_t *view);
static void enable_view_sorting(view_t *view);
static void exclude_in_compare(view_t *view, int selection_only);
static void mark_group(view_t *view, view_t *other, int idx);
static int got_excluded(view_t *view, const dir_entry_t *entry, void *arg);
static int exclude_temporary_entries(view_t *view);
static int is_temporary(view_t *view, const dir_entry_t *entry, void *arg);
static void flist_custom_drop_save(view_t *view);
static uint64_t recalc_entry_size(const dir_entry_t *entry, uint64_t old_size);
static uint64_t entry_calc_nitems(const dir_entry_t *entry);
static void load_dir_list_internal(view_t *view, int reload, int draw_only);
static int populate_dir_list_internal(view_t *view, int reload);
static int populate_custom_view(view_t *view, int reload);
static void re_apply_folds(view_t *view, trie_t *folded_paths);
static int entry_exists(view_t *view, const dir_entry_t *entry, void *arg);
static void zap_compare_view(view_t *view, view_t *other, zap_filter filter,
		void *arg);
static int find_separator(view_t *view, int idx);
static void update_dir_watcher(view_t *view);
static int custom_list_is_incomplete(const view_t *view);
static int is_dead_or_filtered(view_t *view, const dir_entry_t *entry,
		void *arg);
static void update_entries_data(view_t *view);
static void fix_tree_links(dir_entry_t *entries, dir_entry_t *entry,
		int old_idx, int new_idx, int displacement, int correction);
static int is_dir_big(const char path[]);
static void free_view_entries(view_t *view);
static int update_dir_list(view_t *view, int reload);
static void start_dir_list_change(view_t *view, dir_entry_t **entries, int *len,
		int reload);
static void finish_dir_list_change(view_t *view, dir_entry_t *entries, int len);
static int add_file_entry_to_view(const char name[], const void *data,
		void *param);
static void sort_dir_list(int msg, view_t *view);
static void merge_lists(view_t *view, dir_entry_t *entries, int len);
TSTATIC void check_file_uniqueness(view_t *view);
static void add_to_trie(trie_t *trie, view_t *view, dir_entry_t *entry);
static int is_in_trie(trie_t *trie, view_t *view, dir_entry_t *entry,
		void **data);
static void merge_entries(dir_entry_t *new, const dir_entry_t *prev);
static int correct_pos(view_t *view, int pos, int dist, int closest);
static int rescue_from_empty_filelist(view_t *view);
static void add_parent_entry(view_t *view, dir_entry_t **entries, int *count);
static void init_dir_entry(view_t *view, dir_entry_t *entry, const char name[]);
static dir_entry_t * alloc_dir_entry(dir_entry_t **list, int list_size);
static int tree_has_changed(const dir_entry_t *entries, size_t nchildren);
static FSWatchState poll_watcher(fswatch_t *watch, const char path[]);
static void remove_child_entries(view_t *view, dir_entry_t *entry);
static void find_dir_in_cdpath(const char base_dir[], const char dst[],
		char buf[], size_t buf_size);
static entries_t list_sibling_dirs(view_t *view);
static entries_t flist_list_in(view_t *view, const char path[], int only_dirs,
		int can_include_parent);
static dir_entry_t * pick_sibling(view_t *view, entries_t parent_dirs,
		int offset, int wrap, int *wrapped);
static int iter_entries(view_t *view, dir_entry_t **entry, entry_predicate pred,
		int valid_only);
static int mark_selected(view_t *view);
static int set_position_by_path(view_t *view, const char path[]);
static int flist_load_tree_internal(view_t *view, const char path[], int reload,
		int depth);
static int make_tree(view_t *view, const char path[], int reload,
		trie_t *excluded_paths, trie_t *folded_paths, int depth);
static void tree_from_cv(view_t *view);
static int complete_tree(const char name[], int valid, const void *parent_data,
		void *data, void *arg);
static void reset_entry_list(view_t *view, dir_entry_t **entries, int *count);
static void drop_tops(dir_entry_t *entries, int *nentries, int extra);
static int add_files_recursively(view_t *view, const char path[],
		trie_t *excluded_paths, trie_t *folded_paths, int parent_pos,
		int no_direct_parent, int depth);
static FoldState get_fold_state(trie_t *folded_paths, const char full_path[]);
static int set_fold_state(trie_t *folded_paths, const char full_path[],
		FoldState state);
static int entry_is_visible(view_t *view, const char name[], const void *data);
static int tree_candidate_is_visible(view_t *view, const char path[],
		const char name[], int is_dir, int apply_local_filter);
static int add_directory_leaf(view_t *view, const char path[], int parent_pos);
static int init_parent_entry(view_t *view, dir_entry_t *entry,
		const char path[]);

void
init_filelists(void)
{
	fview_setup();

	flist_init_view(&rwin);
	flist_init_view(&lwin);

	curr_view = &lwin;
	other_view = &rwin;
}

void
flist_init_view(view_t *view)
{
	init_flist(view);
	fview_init(view);

	reset_view(view);

	init_view_history(view);
	fview_sorting_updated(view);
}

/* Initializes file list part of the view. */
static void
init_flist(view_t *view)
{
	view->list_pos = 0;
	view->last_seen_pos = -1;
	view->history_num = 0;
	view->history_pos = 0;
	view->on_slow_fs = 0;
	view->has_dups = 0;

	view->watched_dir = NULL;
	view->last_dir = NULL;

	view->matches = 0;

	view->custom.entries = NULL;
	view->custom.entry_count = 0;
	view->custom.orig_dir = NULL;
	view->custom.title = NULL;

	/* Load fake empty element to make dir_entry valid. */
	view->dir_entry = dynarray_cextend(NULL, sizeof(dir_entry_t));
	view->dir_entry[0].name = strdup("");
	view->dir_entry[0].type = FT_DIR;
	view->dir_entry[0].hi_num = -1;
	view->dir_entry[0].name_dec_num = -1;
	view->dir_entry[0].origin = &view->curr_dir[0];
	view->list_rows = 1;
}

void
flist_free_view(view_t *view)
{
	/* For the application, we don't need to zero out fields after freeing them,
	 * but doing so allows reusing this function in tests. */

	free_dir_entries(&view->dir_entry, &view->list_rows);
	free_dir_entries(&view->custom.entries, &view->custom.entry_count);

	update_string(&view->custom.next_title, NULL);
	update_string(&view->custom.orig_dir, NULL);
	update_string(&view->custom.title, NULL);
	trie_free(view->custom.excluded_paths);
	trie_free(view->custom.folded_paths);
	trie_free(view->custom.paths_cache);
	view->custom.excluded_paths = NULL;
	view->custom.folded_paths = NULL;
	view->custom.paths_cache = NULL;

	free_dir_entries(&view->custom.full.entries, &view->custom.full.nentries);

	/* Two pointer fields below don't contain valid data that needs to be freed,
	 * zeroing them for tests and to at least mention them to signal that they
	 * weren't forgotten. */
	view->local_filter.unfiltered = NULL;
	view->local_filter.saved = NULL;
	view->local_filter.unfiltered_count = 0;

	update_string(&view->local_filter.prev, NULL);
	free(view->local_filter.poshist);
	view->local_filter.poshist = NULL;

	filter_dispose(&view->local_filter.filter);
	filter_dispose(&view->auto_filter);
	matcher_free(view->manual_filter);
	view->manual_filter = NULL;
	update_string(&view->prev_manual_filter, NULL);
	update_string(&view->prev_auto_filter, NULL);

	view->custom.type = CV_REGULAR;

	update_string(&view->file_preview_clear_cmd, NULL);

	fswatch_free(view->watch);
	view->watch = NULL;
	update_string(&view->watched_dir, NULL);

	update_string(&view->last_dir, NULL);

	flist_free_cache(&view->left_column);
	flist_free_cache(&view->right_column);

	update_string(&view->last_curr_file, NULL);

	free_string_array(view->saved_selection, view->nsaved_selection);
	view->nsaved_selection = 0;
	view->saved_selection = NULL;

	flist_hist_resize(view, 0);

	cs_reset(&view->cs);

	columns_free(view->columns);
	view->columns = NULL;

	update_string(&view->view_columns, NULL);
	update_string(&view->view_columns_g, NULL);
	update_string(&view->sort_groups, NULL);
	update_string(&view->sort_groups_g, NULL);
	update_string(&view->preview_prg, NULL);
	update_string(&view->preview_prg_g, NULL);

	modview_info_free(view->vi);
	view->vi = NULL;

	if(view->primary_group_set)
	{
		regfree(&view->primary_group);
		view->primary_group_set = 0;
	}

	marks_clear_view(view);

	pthread_mutex_destroy(view->timestamps_mutex);
	free(view->timestamps_mutex);
	view->timestamps_mutex = NULL;
}

void
reset_views(void)
{
	reset_view(&lwin);
	reset_view(&rwin);
}

/* Loads some of view parameters that should be restored on configuration
 * reloading (e.g. on :restart command). */
static void
reset_view(view_t *view)
{
	fview_reset(view);
	filters_view_reset(view);

	view->hide_dot_g = view->hide_dot = 1;

	memset(&view->sort[0], SK_NONE, sizeof(view->sort));
	ui_view_sort_list_ensure_well_formed(view, view->sort);
	memcpy(&view->sort_g[0], &view->sort[0], sizeof(view->sort_g));
}

/* Allocates memory for view history smartly (handles huge values). */
static void
init_view_history(view_t *view)
{
	if(cfg.history_len == 0)
		return;

	view->history = calloc(cfg.history_len, sizeof(history_t));
	while(view->history == NULL)
	{
		cfg.history_len /= 2;
		view->history = calloc(cfg.history_len, sizeof(history_t));
	}
}

void
load_initial_directory(view_t *view, const char dir[])
{
	/* Use current working directory as original location for custom views loaded
	 * from command-line via "-". */
	if(view->curr_dir[0] == '\0' || strcmp(view->curr_dir, "-") == 0)
	{
		copy_str(view->curr_dir, sizeof(view->curr_dir), dir);
	}
	else
	{
		dir = view->curr_dir;
	}

	if(!is_root_dir(view->curr_dir))
	{
		chosp(view->curr_dir);
	}
	if(change_directory(view, dir) < 0)
	{
		leave_invalid_dir(view);
		(void)change_directory(view, view->curr_dir);
	}
}

dir_entry_t *
get_current_entry(const view_t *view)
{
	if(view->list_pos < 0 || view->list_pos >= view->list_rows)
	{
		return NULL;
	}

	return &view->dir_entry[view->list_pos];
}

char *
get_current_file_name(view_t *view)
{
	if(view->list_pos == -1)
	{
		static char empty_string[1];
		return empty_string;
	}
	return get_current_entry(view)->name;
}

void
invert_sorting_order(view_t *view)
{
	if(memcmp(&view->sort_g[0], &view->sort[0], sizeof(view->sort_g)) == 0)
	{
		view->sort_g[0] = -view->sort_g[0];
	}
	view->sort[0] = -view->sort[0];
}

void
leave_invalid_dir(view_t *view)
{
	struct stat s;
	char *const path = view->curr_dir;

	if(fuse_try_updir_from_a_mount(path, view))
	{
		return;
	}

	/* Use stat() directly to skip all possible optimizations.  It might be a bit
	 * heavier, but this is quite rare error-recovery procedure, we can afford it
	 * here.  Even in the worst case of going to the root, this isn't that much
	 * calls. */
	while((os_stat(path, &s) != 0 || !(s.st_mode & S_IFDIR) ||
			!directory_accessible(path)) && is_path_well_formed(path))
	{
		if(fuse_try_updir_from_a_mount(path, view))
		{
			break;
		}

		remove_last_path_component(path);
	}

	ensure_path_well_formed(path);
}

int
navigate_to(view_t *view, const char path[])
{
	if(change_directory(view, path) < 0)
	{
		return 1;
	}

	/* Changing directory can require updating tab line. */
	stats_redraw_later();

	load_dir_list(view, 0);
	fview_cursor_redraw(view);
	return 0;
}

void
navigate_back(view_t *view)
{
	const char *const dest = flist_custom_active(view)
	                       ? view->custom.orig_dir
	                       : view->last_dir;
	if(dest != NULL)
	{
		navigate_to(view, dest);
	}
}

void
navigate_to_file(view_t *view, const char dir[], const char file[],
		int preserve_cv)
{
	if(flist_custom_active(view) && preserve_cv)
	{
		/* Try to find requested element in custom list of files. */
		if(navigate_to_file_in_custom_view(view, dir, file) == 0)
		{
			return;
		}
	}

	/* Do not change directory if we already there. */
	if(!paths_are_equal(view->curr_dir, dir))
	{
		if(change_directory(view, dir) >= 0)
		{
			load_dir_list(view, 0);
		}
	}

	if(paths_are_equal(view->curr_dir, dir))
	{
		(void)fpos_ensure_selected(view, file);
	}
}

/* navigate_to_file() helper that tries to find requested file in custom view.
 * Returns non-zero on failure to find such file, otherwise zero is returned. */
static int
navigate_to_file_in_custom_view(view_t *view, const char dir[],
		const char file[])
{
	char full_path[PATH_MAX + 1];

	snprintf(full_path, sizeof(full_path), "%s/%s", dir, file);

	if(custom_list_is_incomplete(view))
	{
		const dir_entry_t *entry = entry_from_path(view, view->custom.full.entries,
				view->custom.full.nentries, full_path);
		if(entry == NULL)
		{
			/* No such entry in the view at all. */
			return 1;
		}

		if(!local_filter_matches(view, entry))
		{
			/* The item is filtered-out by current settings, undo this filtering. */
			local_filter_remove(view);
			load_dir_list(view, 1);
		}
	}

	if(set_position_by_path(view, full_path) != 0)
	{
		/* File might not exist anymore at that location. */
		return 1;
	}

	ui_view_schedule_redraw(view);
	return 0;
}

int
change_directory(view_t *view, const char directory[])
{
	/* TODO: refactor this big function change_directory(). */

	char dir_dup[PATH_MAX + 1];
	const int was_in_custom_view = flist_custom_active(view);
	int location_changed;

	if(is_dir_list_loaded(view))
	{
		flist_hist_save(view);
	}

	if(is_path_absolute(directory))
	{
		canonicalize_path(directory, dir_dup, sizeof(dir_dup));
	}
	else
	{
		char newdir[PATH_MAX + 1 + NAME_MAX + 1];
		const char *const dir = flist_get_dir(view);
#ifdef _WIN32
		if(directory[0] == '/')
		{
			snprintf(newdir, sizeof(newdir), "%c:%s", dir[0], directory);
		}
		else
#endif
		{
			snprintf(newdir, sizeof(newdir), "%s/%s", dir, directory);
		}
		canonicalize_path(newdir, dir_dup, sizeof(dir_dup));
	}

	system_to_internal_slashes(dir_dup);

	if(cfg.chase_links)
	{
		char real_path[PATH_MAX + 1];
		if(os_realpath(dir_dup, real_path) == real_path)
		{
			/* Do this on success only, if realpath() fails, just go with the original
			 * path. */
			canonicalize_path(real_path, dir_dup, sizeof(dir_dup));
			system_to_internal_slashes(dir_dup);
		}
	}

	if(!is_root_dir(dir_dup))
		chosp(dir_dup);

	if(!is_valid_dir(dir_dup))
	{
		show_error_msgf("Directory Access Error", "Cannot open %s", dir_dup);
		copy_str(view->curr_dir, sizeof(view->curr_dir), dir_dup);
		leave_invalid_dir(view);
		copy_str(dir_dup, sizeof(dir_dup), view->curr_dir);
	}

	location_changed = (stroscmp(dir_dup, flist_get_dir(view)) != 0);

	/* Check if we're exiting from a FUSE mounted top level directory. */
	if(is_parent_dir(directory) && fuse_is_mount_point(view->curr_dir))
	{
		/* No other pane in any tab should be inside subtree that might be
		 * unmounted. */
		if(tabs_visitor_count(view->curr_dir) == 1)
		{
			const int r = fuse_try_unmount(view);
			if(r != 0)
			{
				return r;
			}
		}
		else if(fuse_try_updir_from_a_mount(view->curr_dir, view))
		{
			/* On success fuse_try_updir_from_a_mount() calls change_directory()
			 * recursively to change directory, so we can just leave. */
			return 1;
		}
	}

	/* Clean up any excess separators */
	if(!is_root_dir(view->curr_dir))
		chosp(view->curr_dir);
	if(view->last_dir != NULL && !is_root_dir(view->last_dir))
		chosp(view->last_dir);

#ifndef _WIN32
	if(!path_exists(dir_dup, DEREF))
#else
	if(!is_valid_dir(dir_dup))
#endif
	{
		LOG_SERROR_MSG(errno, "Can't access \"%s\"", dir_dup);
		log_cwd();

		show_error_msgf("Directory Access Error", "Cannot open %s", dir_dup);

		flist_sel_stash(view);
		return -1;
	}

	if(os_access(dir_dup, X_OK) != 0 && !is_unc_root(dir_dup))
	{
		LOG_SERROR_MSG(errno, "Can't access(, X_OK) \"%s\"", dir_dup);
		log_cwd();

		show_error_msgf("Directory Access Error",
				"You do not have execute access on %s", dir_dup);

		flist_sel_stash(view);
		return -1;
	}

	if(os_access(dir_dup, R_OK) != 0 && !is_unc_root(dir_dup))
	{
		LOG_SERROR_MSG(errno, "Can't access(, R_OK) \"%s\"", dir_dup);
		log_cwd();

		if(location_changed)
		{
			show_error_msgf("Directory Access Error",
					"You do not have read access on %s", dir_dup);
		}
	}

	if(vifm_chdir(dir_dup) != 0 && !is_unc_root(dir_dup))
	{
		LOG_SERROR_MSG(errno, "Can't chdir() \"%s\"", dir_dup);
		log_cwd();

		show_error_msgf("Change Directory Error", "Couldn't open %s", dir_dup);
		return -1;
	}

	if(!is_root_dir(dir_dup))
	{
		chosp(dir_dup);
	}

	flist_sel_view_to_reload(view, location_changed ? dir_dup : NULL);

	/* Need to use dir_dup instead of calling get_cwd() to avoid resolving
	 * symbolic links in path. */
	env_set("PWD", dir_dup);

	if(location_changed)
	{
		replace_string(&view->last_dir, flist_get_dir(view));
		view->on_slow_fs = is_on_slow_fs(dir_dup, cfg.slow_fs_list);
	}

	copy_str(view->curr_dir, sizeof(view->curr_dir), dir_dup);

	if(is_dir_list_loaded(view))
	{
		flist_hist_setup(view, NULL, "", -1, -1);
	}

	if(was_in_custom_view)
	{
		on_custom_view_leave(view);
	}

	if(location_changed || was_in_custom_view)
	{
		on_location_change(view, location_changed);
	}
	if(location_changed)
	{
		view->location_changed = 1;
	}
	return 0;
}

/* Performs additional actions on leaving custom view. */
static void
on_custom_view_leave(view_t *view)
{
	if(ui_view_unsorted(view))
	{
		enable_view_sorting(view);
	}

	if(cv_compare(view->custom.type))
	{
		view_t *const other = (view == curr_view) ? other_view : curr_view;

		/* Indicate that this is not a compare view anymore. */
		view->custom.type = CV_REGULAR;

		/* Leave compare mode in both views at the same time. */
		if(other->custom.type == CV_DIFF)
		{
			rn_leave(other, 1);
		}
	}

	trie_free(view->custom.excluded_paths);
	view->custom.excluded_paths = NULL;

	trie_free(view->custom.folded_paths);
	view->custom.folded_paths = NULL;
}

int
is_dir_list_loaded(view_t *view)
{
	dir_entry_t *const entry = (view->list_rows < 1) ? NULL : &view->dir_entry[0];
	return entry != NULL && entry->name[0] != '\0';
}

#ifdef _WIN32
/* Appends list of shares to filelist of the view.  Returns zero on success,
 * otherwise non-zero is returned. */
static int
fill_with_shared(view_t *view)
{
	NET_API_STATUS res;
	wchar_t *wserver;

	wserver = to_wide(view->curr_dir + 2);
	if(wserver == NULL)
	{
		show_error_msg("Memory Error", "Unable to allocate enough memory");
		return 1;
	}

	do
	{
		PSHARE_INFO_0 buf_ptr;
		DWORD er = 0, tr = 0, resume = 0;

		res = NetShareEnum(wserver, 0, (LPBYTE *)&buf_ptr, -1, &er, &tr, &resume);
		if(res == ERROR_SUCCESS || res == ERROR_MORE_DATA)
		{
			PSHARE_INFO_0 p;
			DWORD i;

			for(i = 0, p = buf_ptr; i < er; ++i, ++p)
			{
				dir_entry_t *dir_entry;
				char *utf8_name;

				dir_entry = alloc_dir_entry(&view->dir_entry, view->list_rows);
				if(dir_entry == NULL)
				{
					show_error_msg("Memory Error", "Unable to allocate enough memory");
					continue;
				}

				utf8_name = utf8_from_utf16((wchar_t *)p->shi0_netname);

				init_dir_entry(view, dir_entry, utf8_name);
				dir_entry->type = FT_DIR;

				free(utf8_name);

				++view->list_rows;
			}
			NetApiBufferFree(buf_ptr);
		}
	}
	while(res == ERROR_MORE_DATA);

	free(wserver);

	return (res != ERROR_SUCCESS);
}
#endif

char *
get_typed_entry_fpath(const dir_entry_t *entry)
{
	const char *type_suffix;
	char full_path[PATH_MAX + 1];

	type_suffix = fentry_is_dir(entry) ? "/" : "";

	get_full_path_of(entry, sizeof(full_path), full_path);
	return format_str("%s%s", full_path, type_suffix);
}

int
flist_custom_active(const view_t *view)
{
	/* First check isn't enough on startup, which leads to false positives.  Yet
	 * this implicit condition seems to be preferable to omit introducing function
	 * that would terminate custom view mode. */
	return view->curr_dir[0] == '\0' && !is_null_or_empty(view->custom.orig_dir);
}

void
flist_custom_start(view_t *view, const char title[])
{
	free_dir_entries(&view->custom.entries, &view->custom.entry_count);
	(void)replace_string(&view->custom.next_title, title);

	trie_free(view->custom.paths_cache);
	view->custom.paths_cache = trie_create(/*free_func=*/NULL);
}

dir_entry_t *
flist_custom_add(view_t *view, const char path[])
{
	char canonic_path[PATH_MAX + 1];
	to_canonic_path(path, flist_get_dir(view), canonic_path,
			sizeof(canonic_path));

	/* Don't add duplicates. */
	if(trie_put(view->custom.paths_cache, canonic_path) != 0)
	{
		return NULL;
	}

	return entry_list_add(view, &view->custom.entries, &view->custom.entry_count,
			canonic_path);
}

dir_entry_t *
flist_custom_put(view_t *view, dir_entry_t *entry)
{
	char full_path[PATH_MAX + 1];
	dir_entry_t *dir_entry;
	size_t list_size = view->custom.entry_count;

	get_full_path_of(entry, sizeof(full_path), full_path);

	/* Don't add duplicates. */
	if(trie_put(view->custom.paths_cache, full_path) != 0)
	{
		return NULL;
	}

	dir_entry = add_dir_entry(&view->custom.entries, &list_size, entry);
	view->custom.entry_count = list_size;
	return dir_entry;
}

void
flist_custom_add_separator(view_t *view, int id)
{
	dir_entry_t *const dir_entry = alloc_dir_entry(&view->custom.entries,
			view->custom.entry_count);
	if(dir_entry != NULL)
	{
		init_dir_entry(view, dir_entry, "");
		dir_entry->origin = strdup(flist_get_dir(view));
		dir_entry->owns_origin = 1;
		dir_entry->id = id;
		++view->custom.entry_count;
	}
}

#ifndef _WIN32

/* Fills directory entry with information about file specified by the path.
 * Returns non-zero on error, otherwise zero is returned. */
static int
fill_dir_entry_by_path(dir_entry_t *entry, const char path[])
{
	return fill_dir_entry(entry, path, NULL);
}

/* Fills fields of the entry from stat information of the file specified by its
 * path.  d is optional source of file type.  Returns zero on success, otherwise
 * non-zero is returned. */
static int
fill_dir_entry(dir_entry_t *entry, const char path[], const struct dirent *d)
{
	struct stat s;

	/* Load the inode information or leave blank values in the entry. */
	if(os_lstat(path, &s) != 0)
	{
		LOG_SERROR_MSG(errno, "Can't lstat() \"%s\"", path);
		return 1;
	}

	entry->type = get_type_from_mode(s.st_mode);
	if(entry->type == FT_UNK)
	{
		entry->type = (d == NULL) ? FT_UNK : type_from_dir_entry(d, path);
	}
	if(entry->type == FT_UNK)
	{
		LOG_ERROR_MSG("Can't determine type of \"%s\"", path);
		return 1;
	}

	entry->size = (uintmax_t)s.st_size;
	entry->uid = s.st_uid;
	entry->gid = s.st_gid;
	entry->mode = s.st_mode;
	entry->inode = s.st_ino;
	entry->mtime = s.st_mtime;
	entry->atime = s.st_atime;
	entry->ctime = s.st_ctime;
	entry->nlinks = s.st_nlink;

	if(entry->type == FT_LINK)
	{
		struct stat s;

		const SymLinkType symlink_type = get_symlink_type(path);
		entry->dir_link = (symlink_type != SLT_UNKNOWN);
		entry->slow_target = (symlink_type == SLT_SLOW);

		/* Query mode of symbolic link target. */
		if(!entry->slow_target && os_stat(entry->name, &s) == 0)
		{
			entry->mode = s.st_mode;
		}
	}

	return 0;
}

/* Checks whether file is a directory.  Returns non-zero if so, otherwise zero
 * is returned. */
static int
data_is_dir_entry(const struct dirent *d, const char path[])
{
	return is_dirent_targets_dir(path, d);
}

#else

/* Fills directory entry with information about file specified by the path.
 * Returns non-zero on error, otherwise zero is returned. */
static int
fill_dir_entry_by_path(dir_entry_t *entry, const char path[])
{
	wchar_t *utf16_path;
	HANDLE hfind;
	WIN32_FIND_DATAW ffd;

	utf16_path = utf8_to_utf16(path);
	hfind = FindFirstFileW(utf16_path, &ffd);
	free(utf16_path);

	if(hfind == INVALID_HANDLE_VALUE)
	{
		return 1;
	}

	fill_dir_entry(entry, path, &ffd);

	FindClose(hfind);

	return 0;
}

/* Fills fields of the entry from *ffd fields for the file specified by its
 * path.  type_hint is additional source of file type.  Returns zero on success,
 * Returns zero on success, otherwise non-zero is returned. */
static int
fill_dir_entry(dir_entry_t *entry, const char path[],
		const WIN32_FIND_DATAW *ffd)
{
	entry->size = (ffd->nFileSizeHigh*((uint64_t)MAXDWORD + 1))
	            + ffd->nFileSizeLow;
	entry->attrs = ffd->dwFileAttributes;
	entry->mtime = win_to_unix_time(ffd->ftLastWriteTime);
	entry->atime = win_to_unix_time(ffd->ftLastAccessTime);
	entry->ctime = win_to_unix_time(ffd->ftCreationTime);

	if(is_win_symlink(ffd->dwFileAttributes, ffd->dwReserved0))
	{
		const SymLinkType symlink_type = get_symlink_type(path);
		entry->dir_link = (symlink_type != SLT_UNKNOWN);
		entry->slow_target = (symlink_type == SLT_SLOW);

		entry->type = FT_LINK;
	}
	else if(ffd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
	{
		/* Windows doesn't like returning size of directories when it can. */
		entry->size = get_file_size(entry->name);
		entry->type = FT_DIR;
	}
	else if(is_win_executable(path))
	{
		entry->type = FT_EXEC;
	}
	else
	{
		entry->type = FT_REG;
	}

	return 0;
}

/* Checks whether file is a directory.  Returns non-zero if so, otherwise zero
 * is returned. */
static int
data_is_dir_entry(const WIN32_FIND_DATAW *ffd, const char path[])
{
	return (ffd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}

#endif

int
flist_custom_finish(view_t *view, CVType type, int allow_empty)
{
	return flist_custom_finish_internal(view, type, 0, flist_get_dir(view),
			allow_empty);
}

/* Finishes file list population, handles empty resulting list corner case.
 * reload flag suppresses actions taken on location change.  dir is current
 * directory of the view.  Returns zero on success, otherwise (on empty list)
 * non-zero is returned. */
static int
flist_custom_finish_internal(view_t *view, CVType type, int reload,
		const char dir[], int allow_empty)
{
	enum { NORMAL, CUSTOM, UNSORTED } previous;
	const int empty_view = (view->custom.entry_count == 0);

	trie_free(view->custom.paths_cache);
	view->custom.paths_cache = NULL;

	if(empty_view && !allow_empty)
	{
		free_dir_entries(&view->custom.entries, &view->custom.entry_count);
		update_string(&view->custom.next_title, NULL);
		return 1;
	}

	free(view->custom.title);
	view->custom.title = view->custom.next_title;
	view->custom.next_title = NULL;

	/* If there are no files and we are allowed to add ".." directory, do it. */
	if(empty_view || (!cv_unsorted(type) && cfg_parent_dir_is_visible(0)))
	{
		dir_entry_t *const dir_entry = alloc_dir_entry(&view->custom.entries,
				view->custom.entry_count);
		if(dir_entry != NULL)
		{
			init_dir_entry(view, dir_entry, "..");
			dir_entry->type = FT_DIR;
			dir_entry->origin = strdup(dir);
			dir_entry->owns_origin = 1;
			++view->custom.entry_count;
		}
	}

	previous = (view->curr_dir[0] != '\0')
	         ? NORMAL
	         : (ui_view_unsorted(view) ? UNSORTED : CUSTOM);

	if(previous == NORMAL)
	{
		/* Save current location in the directory before replacing it with custom
		 * view. */
		if(is_dir_list_loaded(view))
		{
			flist_hist_save(view);
		}

		(void)replace_string(&view->custom.orig_dir, dir);
		view->curr_dir[0] = '\0';
	}

	/* Replace view file list with custom list. */
	free_dir_entries(&view->dir_entry, &view->list_rows);
	view->dir_entry = view->custom.entries;
	view->list_rows = view->custom.entry_count;
	view->custom.entries = NULL;
	view->custom.entry_count = 0;
	view->dir_entry = dynarray_shrink(view->dir_entry);
	view->filtered = 0;
	view->matches = 0;

	/* Kind of custom view must be set to correct value before option loading and
	 * sorting. */
	view->custom.type = type;

	if(cv_unsorted(type))
	{
		/* Disabling sorting twice in a row erases sorting completely. */
		if(previous != UNSORTED)
		{
			disable_view_sorting(view);
		}
	}
	else if(previous == UNSORTED)
	{
		enable_view_sorting(view);
	}

	flist_custom_drop_save(view);

	if(!reload)
	{
		on_location_change(view, 0);
		if(cfg.cvoptions & CVO_AUTOCMDS)
		{
			vle_aucmd_execute("DirEnter", flist_get_dir(view), view);
		}
	}

	sort_dir_list(0, view);

	ui_view_schedule_redraw(view);
	fpos_ensure_valid_pos(view);

	return 0;
}

/* Perform actions on view location change.  Force activates all actions
 * unconditionally otherwise they are checked against cvoptions. */
static void
on_location_change(view_t *view, int force)
{
	view->has_dups = 0;

	flist_custom_drop_save(view);

	if(force || (cfg.cvoptions & CVO_LOCALFILTER))
	{
		filters_dir_updated(view);
	}
	/* Stage check is to skip body of the if in tests. */
	if(curr_stats.load_stage > 0 && (force || (cfg.cvoptions & CVO_LOCALOPTS)))
	{
		reset_local_options(view);
	}
}

/* Disables view sorting saving its state for the future. */
static void
disable_view_sorting(view_t *view)
{
	memcpy(&view->custom.sort[0], &view->sort[0], sizeof(view->custom.sort));
	memset(&view->sort[0], SK_NONE, sizeof(view->sort));
	load_sort_option(view);
}

/* Undoes was was done by disable_view_sorting(). */
static void
enable_view_sorting(view_t *view)
{
	memcpy(&view->sort[0], &view->custom.sort[0], sizeof(view->sort));
	load_sort_option(view);
}

void
flist_custom_exclude(view_t *view, int selection_only)
{
	flist_set_marking(view, 0);

	if(!flist_custom_active(view))
	{
		return;
	}

	if(cv_compare(view->custom.type))
	{
		exclude_in_compare(view, selection_only);
		return;
	}

	dir_entry_t *entry = NULL;
	trie_t *excluded = trie_create(/*free_func=*/NULL);
	while(iter_marked_entries(view, &entry))
	{
		char full_path[PATH_MAX + 1];

		entry->temporary = 1;

		get_full_path_of(entry, sizeof(full_path), full_path);
		(void)trie_put(excluded, full_path);

		if(cv_tree(view->custom.type))
		{
			(void)trie_put(view->custom.excluded_paths, full_path);
		}
	}

	/* Update copy of full list of entries (it might be empty, which is OK),
	 * because exclusion is not reversible. */
	(void)zap_entries(view, view->custom.full.entries,
			&view->custom.full.nentries, &got_excluded, excluded, 1, 1);
	trie_free(excluded);

	(void)exclude_temporary_entries(view);
}

/* Removes marked files from compare view.  Zero selection_only enables
 * excluding files that share ids with selected items. */
static void
exclude_in_compare(view_t *view, int selection_only)
{
	view_t *const other = (view == curr_view) ? other_view : curr_view;
	const int double_compare = (view->custom.type == CV_DIFF);
	const int n = other->list_rows;
	dir_entry_t *entry = NULL;
	while(iter_marked_entries(view, &entry))
	{
		if(selection_only)
		{
			entry->temporary = 1;
			if(double_compare)
			{
				other->dir_entry[entry - view->dir_entry].temporary = 1;
			}
		}
		else
		{
			mark_group(view, other, entry - view->dir_entry);
		}
	}

	(void)exclude_temporary_entries(view);
	if(double_compare && exclude_temporary_entries(other) == n)
	{
		/* Leave compare mode if we excluded all files. */
		rn_leave(view, 1);
	}
}

/* Selects all neighbours of the idx-th element that share its id. */
static void
mark_group(view_t *view, view_t *other, int idx)
{
	int i;
	int id;

	if(view->dir_entry[idx].temporary)
	{
		return;
	}

	id = view->dir_entry[idx].id;

	for(i = idx - 1; i >= 0 && view->dir_entry[i].id == id; --i)
	{
		view->dir_entry[i].temporary = 1;
		if(view->custom.type == CV_DIFF)
		{
			other->dir_entry[i].temporary = 1;
		}
	}
	for(i = idx; i < view->list_rows && view->dir_entry[i].id == id; ++i)
	{
		view->dir_entry[i].temporary = 1;
		if(view->custom.type == CV_DIFF)
		{
			other->dir_entry[i].temporary = 1;
		}
	}
}

/* zap_entries() filter to filter-out files, which were just excluded from the
 * view.  Returns non-zero if entry is to be kept and zero otherwise. */
static int
got_excluded(view_t *view, const dir_entry_t *entry, void *arg)
{
	void *data;
	trie_t *const excluded = arg;
	int excluded_entry;

	char full_path[PATH_MAX + 1];
	get_full_path_of(entry, sizeof(full_path), full_path);

	excluded_entry = (trie_get(excluded, full_path, &data) == 0);
	return !excluded_entry;
}

/* Excludes view entries that are marked as "temporary".  Returns number of
 * items that were visible before. */
static int
exclude_temporary_entries(view_t *view)
{
	const int n = zap_entries(view, view->dir_entry, &view->list_rows,
			&is_temporary, NULL, 0, 1);
	(void)zap_entries(view, view->custom.entries, &view->custom.entry_count,
			&is_temporary, NULL, 1, 1);

	fpos_ensure_valid_pos(view);
	ui_view_schedule_redraw(view);

	return n;
}

/* zap_entries() filter to filter-out files, which were marked for removal.
 * Returns non-zero if entry is to be kept and zero otherwise.*/
static int
is_temporary(view_t *view, const dir_entry_t *entry, void *arg)
{
	return !entry->temporary;
}

void
flist_custom_clone(view_t *to, const view_t *from, int as_tree)
{
	dir_entry_t *dst, *src;
	int nentries;
	int i, j;
	const int from_tree = cv_tree(from->custom.type);

	assert(flist_custom_active(from) && to->custom.paths_cache == NULL &&
			"Wrong state of destination view.");

	replace_string(&to->custom.orig_dir, from->custom.orig_dir);
	to->curr_dir[0] = '\0';

	replace_string(&to->custom.title,
			(from_tree && !as_tree) ? "from tree" : from->custom.title);
	to->custom.type = (ui_view_unsorted(from) || from_tree)
	                ? (as_tree ? CV_CUSTOM_TREE : CV_VERY)
	                : CV_REGULAR;
	const int dst_is_tree = cv_tree(to->custom.type);

	if(custom_list_is_incomplete(from))
	{
		src = from->custom.full.entries;
		nentries = from->custom.full.nentries;
	}
	else
	{
		src = from->dir_entry;
		nentries = from->list_rows;
	}

	dst = dynarray_extend(NULL, nentries*sizeof(*dst));

	j = 0;
	for(i = 0; i < nentries; ++i)
	{
		if(!dst_is_tree && src[i].child_pos != 0 && is_parent_dir(src[i].name))
		{
			continue;
		}

		dst[j] = src[i];
		dst[j].name = strdup(dst[j].name);
		dst[j].origin = (dst[j].owns_origin ? strdup(dst[j].origin) : to->curr_dir);

		if(!dst_is_tree)
		{
			/* As destination pane won't be a tree, erase tree-specific data, because
			 * some tree-specific code is driven directly by these fields. */
			dst[j].child_count = 0;
			dst[j].child_pos = 0;
			dst[j].folded = 0;
		}

		++j;
	}

	free_dir_entries(&to->custom.entries, &to->custom.entry_count);
	free_dir_entries(&to->dir_entry, &to->list_rows);
	to->dir_entry = dst;
	to->list_rows = j;

	to->filtered = 0;

	if(ui_view_unsorted(to))
	{
		disable_view_sorting(to);
	}
}

void
flist_custom_uncompress_tree(view_t *view)
{
	unsigned int i;

	assert(cv_tree(view->custom.type) && "This function is for tree-view only!");

	dir_entry_t *entries = view->dir_entry;
	size_t nentries = view->list_rows;
	int restore_parent = 0;

	fsdata_t *const tree = fsdata_create(0, 0);

	view->dir_entry = NULL;
	view->list_rows = 0;

	for(i = 0U; i < nentries; ++i)
	{
		char full_path[PATH_MAX + 1];
		void *data = &entries[i];

		if(entries[i].child_pos == 0 && is_parent_dir(entries[i].name))
		{
			fentry_free(&entries[i]);
			restore_parent = 1;
			continue;
		}

		get_full_path_of(&entries[i], sizeof(full_path), full_path);
		fsdata_set(tree, full_path, &data, sizeof(data));
	}

	if(fsdata_traverse(tree, &complete_tree, view) != 0)
	{
		reset_entry_list(view, &view->dir_entry, &view->list_rows);
		restore_parent = 0;
	}

	fsdata_free(tree);
	dynarray_free(entries);

	drop_tops(view->dir_entry, &view->list_rows, 0);

	if(restore_parent)
	{
		add_parent_dir(view);
	}
}

void
flist_custom_save(view_t *view)
{
	if(flist_is_fs_backed(view))
	{
		flist_custom_drop_save(view);
		return;
	}

	if(view->custom.full.nentries == 0)
	{
		replace_dir_entries(view, &view->custom.full.entries,
				&view->custom.full.nentries, view->dir_entry, view->list_rows);

		/* Some flags shouldn't be copied to the full list. */
		int i;
		for(i = 0; i < view->list_rows; ++i)
		{
			view->custom.full.entries[i].selected = 0;
			view->custom.full.entries[i].folded = 0;
		}
	}
}

/* Erases list previously saved by flist_custom_save(). */
static void
flist_custom_drop_save(view_t *view)
{
	free_dir_entries(&view->custom.full.entries, &view->custom.full.nentries);
}

const char *
flist_get_dir(const view_t *view)
{
	if(flist_custom_active(view))
	{
		assert(view->custom.orig_dir != NULL && "Wrong view dir state.");
		return view->custom.orig_dir;
	}

	return view->curr_dir;
}

void
flist_goto_by_path(view_t *view, const char path[])
{
	char full_path[PATH_MAX + 1];
	const char *const name = get_last_path_component(path);

	get_current_full_path(view, sizeof(full_path), full_path);
	if(stroscmp(full_path, path) == 0)
	{
		return;
	}

	if(flist_custom_active(view) && cv_tree(view->custom.type) &&
			strcmp(name, "..") == 0)
	{
		int pos;
		char dir_only[PATH_MAX + 1];

		snprintf(dir_only, sizeof(dir_only), "%.*s", (int)(name - path), path);
		pos = fpos_find_entry(view, name, dir_only);
		if(pos != -1)
		{
			view->list_pos = pos;
		}
		return;
	}

	(void)set_position_by_path(view, path);
}

dir_entry_t *
entry_from_path(view_t *view, dir_entry_t *entries, int count,
		const char path[])
{
	char canonic_path[PATH_MAX + 1];
	const char *fname;
	int i;

	to_canonic_path(path, flist_get_dir(view), canonic_path,
			sizeof(canonic_path));

	fname = get_last_path_component(canonic_path);
	for(i = 0; i < count; ++i)
	{
		char full_path[PATH_MAX + 1];
		dir_entry_t *const entry = &entries[i];

		if(stroscmp(entry->name, fname) != 0)
		{
			continue;
		}

		get_full_path_of(entry, sizeof(full_path), full_path);
		if(stroscmp(full_path, canonic_path) == 0)
		{
			return entry;
		}
	}

	return NULL;
}

uint64_t
fentry_get_nitems(const view_t *view, const dir_entry_t *entry)
{
	uint64_t nitems;
	fentry_get_dir_info(view, entry, NULL, &nitems);
	return nitems;
}

void
fentry_get_dir_info(const view_t *view, const dir_entry_t *entry,
		uint64_t *size, uint64_t *nitems)
{
	const int is_slow_fs = view->on_slow_fs || entry->slow_target;
	dcache_result_t size_res, nitems_res;

	assert((size != NULL || nitems != NULL) &&
			"At least one of out parameters has to be non-NULL.");

	dcache_get_of(entry, (size == NULL ? NULL : &size_res),
			(nitems == NULL ? NULL : &nitems_res));

	if(size != NULL)
	{
		*size = size_res.value;
		if(size_res.value != DCACHE_UNKNOWN && !size_res.is_valid && !is_slow_fs)
		{
			*size = recalc_entry_size(entry, size_res.value);
		}
	}

	if(nitems != NULL)
	{
		if(!nitems_res.is_valid && !is_slow_fs)
		{
			nitems_res.value = entry_calc_nitems(entry);
		}

		*nitems = (nitems_res.value == DCACHE_UNKNOWN ? 0 : nitems_res.value);
	}
}

/* Updates cached size of a directory also updating its relevant parents.
 * Returns current size of the directory entry. */
static uint64_t
recalc_entry_size(const dir_entry_t *entry, uint64_t old_size)
{
	uint64_t size;

	char full_path[PATH_MAX + 1];
	get_full_path_of(entry, sizeof(full_path), full_path);

	size = fops_dir_size(full_path, 0, &ui_cancellation_info);
	dcache_update_parent_sizes(full_path, size - old_size);

	return size;
}

/* Calculates number of items at path specified by the entry.  No check for file
 * type is performed.  Returns the number, which is zero for files. */
static uint64_t
entry_calc_nitems(const dir_entry_t *entry)
{
	char full_path[PATH_MAX + 1];
	get_full_path_of(entry, sizeof(full_path), full_path);

	uint64_t ret = count_dir_items(full_path);

	uint64_t inode = get_true_inode(entry);
	dcache_set_at(full_path, inode, DCACHE_UNKNOWN, ret);

	return ret;
}

int
populate_dir_list(view_t *view, int reload)
{
	const int result = populate_dir_list_internal(view, reload);
	if(view->list_pos > view->list_rows - 1)
	{
		view->list_pos = view->list_rows - 1;
	}
	return result;
}

void
load_dir_list(view_t *view, int reload)
{
	load_dir_list_internal(view, reload, 0);
}

/* Loads file list for the view and redraws the view.  The reload parameter
 * should be set in case of view refresh operation.  The draw_only parameter
 * prevents extra actions that might be taken care of outside this function as
 * well. */
static void
load_dir_list_internal(view_t *view, int reload, int draw_only)
{
	if(populate_dir_list(view, reload) != 0)
	{
		return;
	}

	if(draw_only)
	{
		draw_dir_list_only(view);
	}
	else
	{
		draw_dir_list(view);
	}

	if(view == curr_view)
	{
		if(strnoscmp(view->curr_dir, cfg.fuse_home, strlen(cfg.fuse_home)) == 0 &&
				stroscmp(other_view->curr_dir, view->curr_dir) == 0)
		{
			load_dir_list(other_view, 1);
		}
	}
}

/* Loads filelist for the view.  The reload parameter should be set in case of
 * view refresh operation.  Returns non-zero on error. */
static int
populate_dir_list_internal(view_t *view, int reload)
{
	char *saved_cwd;

	view->filtered = 0;

	/* List reload usually implies that something related to file list has
	 * changed, like an option.  Reset cached lists to make sure they are up to
	 * date with main column. */
	if(reload)
	{
		flist_free_cache(&view->left_column);
		flist_free_cache(&view->right_column);
	}

	if(flist_custom_active(view))
	{
		return populate_custom_view(view, reload);
	}

	if(!reload && is_dir_big(view->curr_dir) && !modes_is_cmdline_like())
	{
		ui_sb_quick_msgf("%s", "Reading directory...");
	}

	if(curr_stats.load_stage < 2)
	{
		update_all_windows();
	}

	saved_cwd = save_cwd();
	/* this is needed for lstat() below */
	if(vifm_chdir(view->curr_dir) != 0 && !is_unc_root(view->curr_dir))
	{
		LOG_SERROR_MSG(errno, "Can't chdir() into \"%s\"", view->curr_dir);
		restore_cwd(saved_cwd);
		return 1;
	}

	/* If directory didn't change. */
	if(view->watch != NULL && view->watched_dir != NULL &&
			stroscmp(view->watched_dir, view->curr_dir) == 0)
	{
		/* Drain all events that happened before this point. */
		(void)poll_watcher(view->watch, view->curr_dir);
	}

	if(is_unc_root(view->curr_dir))
	{
#ifdef _WIN32
		free_view_entries(view);
		if(fill_with_shared(view) == 0)
		{
			if(view->list_rows == 0)
			{
				add_parent_dir(view);
			}
		}
		else
		{
			show_error_msgf("Share enumeration error",
					"Can't load list of shares of %s", view->curr_dir);

			leave_invalid_dir(view);
			if(update_dir_list(view, reload) != 0)
			{
				/* We don't have read access, only execute, or there were other
				 * problems. */
				free_view_entries(view);
				add_parent_dir(view);
			}
		}
#endif
	}
	else if(update_dir_list(view, reload) != 0)
	{
		/* We don't have read access, only execute, or there were other problems. */
		free_view_entries(view);
		add_parent_dir(view);
	}

	if(!reload && !modes_is_cmdline_like())
	{
		ui_sb_clear();
	}

	fview_update_geometry(view);

	/* If reloading the same directory don't jump to history position.  Stay at
	 * the current line. */
	if(!reload)
	{
		/* XXX: why cursor is positioned in code that loads the list? */
		flist_hist_lookup(view, view);
	}

	if(view->location_changed)
	{
		fview_dir_updated(view);
	}

	if(view->list_rows < 1)
	{
		if(rescue_from_empty_filelist(view))
		{
			restore_cwd(saved_cwd);
			return 0;
		}

		add_parent_dir(view);
	}

	fview_list_updated(view);

	/* Because we reset directory watcher if directory didn't change before
	 * loading file list, it's possible that we did load everything, but one more
	 * update will happen anyway afterwards.  We shouldn't drain change event
	 * here, because it makes it possible to skip an update (when directory was
	 * changed while we were reading from it). */
	update_dir_watcher(view);

	if(view->location_changed)
	{
		view->location_changed = 0;
		vle_aucmd_execute("DirEnter", view->curr_dir, view);
	}

	restore_cwd(saved_cwd);
	return 0;
}

/* (Re)loads custom view file list.  Returns non-zero on error. */
static int
populate_custom_view(view_t *view, int reload)
{
	if(view->custom.type == CV_TREE)
	{
		dir_entry_t *prev_dir_entries;
		int prev_list_rows, result;

		start_dir_list_change(view, &prev_dir_entries, &prev_list_rows, reload);
		result = flist_load_tree_internal(view, flist_get_dir(view), 1, INT_MAX);

		if(view->dir_entry == NULL)
		{
			/* Restore original list in case of failure. */
			view->dir_entry = prev_dir_entries;
			view->list_rows = result;
		}
		else
		{
			finish_dir_list_change(view, prev_dir_entries, prev_list_rows);
		}

		if(result != 0)
		{
			show_error_msg("Tree View", "Reload failed");
		}

		return result;
	}

	if(view->custom.type == CV_DIFF)
	{
		if(filter_in_compare(view, NULL, &entry_exists))
		{
			return 0;
		}
	}
	else
	{
		if(custom_list_is_incomplete(view))
		{
			dir_entry_t *prev_dir_entries;
			int prev_list_rows;

			start_dir_list_change(view, &prev_dir_entries, &prev_list_rows, reload);

			replace_dir_entries(view, &view->dir_entry, &view->list_rows,
					view->custom.full.entries, view->custom.full.nentries);

			/* We're merging instead of simply replacing entries to account for
			 * selection and possibly other attributes of entries.  Merging also takes
			 * care of cursor position. */
			finish_dir_list_change(view, prev_dir_entries, prev_list_rows);
		}

		flist_custom_save(view);

		re_apply_folds(view, view->custom.folded_paths);

		(void)zap_entries(view, view->dir_entry, &view->list_rows,
				&is_dead_or_filtered, NULL, 0, 0);
	}

	update_entries_data(view);
	sort_dir_list(!reload, view);
	fview_list_updated(view);
	return 0;
}

/* Applies folds after reloading of a custom view list. */
static void
re_apply_folds(view_t *view, trie_t *folded_paths)
{
	if(view->custom.type != CV_CUSTOM_TREE)
	{
		return;
	}

	int i;
	for(i = 0; i < view->list_rows; ++i)
	{
		dir_entry_t *entry = &view->dir_entry[i];
		if(entry->type != FT_DIR)
		{
			continue;
		}

		/* Just folded entry or an old one whose visibility hasn't changed. */
		if(entry->folded)
		{
			remove_child_entries(view, entry);
			continue;
		}

		char full_path[PATH_MAX + 1];
		get_full_path_of(entry, sizeof(full_path), full_path);

		FoldState state = get_fold_state(folded_paths, full_path);
		if(state == FOLD_USER_CLOSED || state == FOLD_AUTO_CLOSED)
		{
			/* Previously folded entry that has just become visible. */
			remove_child_entries(view, entry);
			entry->folded = 1;
		}
	}
}

int
filter_in_compare(view_t *view, void *arg, zap_filter filter)
{
	view_t *const other = (view == curr_view) ? other_view : curr_view;

	zap_compare_view(view, other, filter, arg);
	if(view->list_rows == 0)
	{
		/* Load views before showing the message as event loop in message dialog can
		 * try to reload views. */
		rn_leave(view, 1);
		show_error_msg("Comparison", "No files left in the views, left the mode.");
		return 1;
	}

	exclude_temporary_entries(other);
	return 0;
}

/* Checks whether entry refers to non-existing file.  Returns non-zero if so,
 * otherwise zero is returned. */
static int
entry_exists(view_t *view, const dir_entry_t *entry, void *arg)
{
	return path_exists_at(entry->origin, entry->name, NODEREF);
}

/* Removes entries that refer to non-existing files from compare view marking
 * corresponding entries of the other view as temporary.  This is a
 * zap_entries() tailored for needs of compare, because that function is already
 * too complex. */
static void
zap_compare_view(view_t *view, view_t *other, zap_filter filter, void *arg)
{
	int i, j = 0;

	for(i = 0; i < view->list_rows; ++i)
	{
		dir_entry_t *const entry = &view->dir_entry[i];

		if(!fentry_is_fake(entry) && !filter(view, entry, arg))
		{
			if(entry->selected)
			{
				--view->selected_files;
			}

			const int separator = find_separator(other, i);
			if(separator >= 0)
			{
				fentry_free(entry);
				other->dir_entry[separator].temporary = 1;

				if(view->list_pos == i)
				{
					view->list_pos = j;
				}
				continue;
			}
			replace_string(&entry->name, "");
			entry->type = FT_UNK;
			entry->id = other->dir_entry[i].id;
		}

		if(i != j)
		{
			view->dir_entry[j] = view->dir_entry[i];
		}

		++j;
	}

	view->list_rows = j;
}

/* Finds separator among the group of equivalent files of the view specified by
 * its position.  Returns index of the separator or -1. */
static int
find_separator(view_t *view, int idx)
{
	int i;
	const int id = view->dir_entry[idx].id;

	for(i = idx; i >= 0 && view->dir_entry[i].id == id; --i)
	{
		if(!view->dir_entry[i].temporary && fentry_is_fake(&view->dir_entry[i]))
		{
			return i;
		}
	}
	for(i = idx + 1; i < view->list_rows && view->dir_entry[i].id == id; ++i)
	{
		if(!view->dir_entry[i].temporary && fentry_is_fake(&view->dir_entry[i]))
		{
			return i;
		}
	}
	return -1;
}

/* Updates directory watcher of the view. */
static void
update_dir_watcher(view_t *view)
{
	const char *const curr_dir = flist_get_dir(view);

	if(view->watch == NULL || view->watched_dir == NULL ||
			stroscmp(view->watched_dir, curr_dir) != 0)
	{
		fswatch_free(view->watch);
		view->watch = fswatch_create(curr_dir);

		/* Failure to create a watch is bad, but there isn't much we can do here and
		 * this doesn't feel like a reason to block anything else. */
		if(view->watch != NULL)
		{
			replace_string(&view->watched_dir, curr_dir);
		}
	}
}

/* Checks whether currently loaded custom list of files is missing some files
 * compared to the original custom list.  Returns non-zero if so, otherwise zero
 * is returned. */
static int
custom_list_is_incomplete(const view_t *view)
{
	if(view->custom.full.nentries == 0)
	{
		return 0;
	}

	if(view->list_rows == 1 && is_parent_dir(view->dir_entry[0].name) &&
			!(view->custom.full.nentries == 1 &&
				is_parent_dir(view->custom.full.entries[0].name)))
	{
		return 1;
	}

	return (view->list_rows != view->custom.full.nentries);
}

/* zap_entries() filter to filter-out inexistent files or files which names
 * match local filter. */
static int
is_dead_or_filtered(view_t *view, const dir_entry_t *entry, void *arg)
{
	if(!path_exists_at(entry->origin, entry->name, NODEREF))
	{
		return 0;
	}

	if(local_filter_matches(view, entry))
	{
		return 1;
	}

	++view->filtered;
	return 0;
}

/* Re-read meta-data for each entry (does nothing for entries on which querying
 * fails). */
static void
update_entries_data(view_t *view)
{
	int i;
	for(i = 0; i < view->list_rows; ++i)
	{
		char full_path[PATH_MAX + 1];
		dir_entry_t *const entry = &view->dir_entry[i];

		/* Fake entries do not map onto files in file system. */
		if(fentry_is_fake(entry))
		{
			continue;
		}

		get_full_path_of(entry, sizeof(full_path), full_path);

		/* Do not care about possible failure, just use previous meta-data. */
		(void)fill_dir_entry_by_path(entry, full_path);
	}
}

int
zap_entries(view_t *view, dir_entry_t *entries, int *count, zap_filter filter,
		void *arg, int allow_empty_list, int remove_subtrees)
{
	int i, j;

	j = 0;
	for(i = 0; i < *count; ++i)
	{
		dir_entry_t *const entry = &entries[i];
		const int nremoved = remove_subtrees ? (entry->child_count + 1) : 1;

		if(filter(view, entry, arg))
		{
			/* We're keeping this entry. */
			if(i != j)
			{
				entries[j] = entries[i];
			}

			++j;
			continue;
		}

		if(entry->selected && view->dir_entry == entries)
		{
			--view->selected_files;
		}

		/* Reassign children of node about to be deleted to its parent.  Child count
		 * doesn't need an update here because these nodes are already counted. */
		int pos = i + 1;
		while(pos < i + 1 + entry->child_count)
		{
			if(entry->child_pos == 0)
			{
				entries[pos].child_pos = 0;
			}
			else
			{
				entries[pos].child_pos += entry->child_pos - 1;
			}
			pos += entries[pos].child_count + 1;
		}

		fix_tree_links(entries, entry, i, j, 0, -nremoved);

		int k;
		for(k = 0; k < nremoved; ++k)
		{
			fentry_free(&entry[k]);
		}

		/* If we're removing file from main list of entries and cursor is right on
		 * this file, move cursor at position this file would take in resulting
		 * list. */
		if(entries == view->dir_entry && view->list_pos >= i &&
				view->list_pos < i + nremoved)
		{
			view->list_pos = j;
		}

		/* Add directory leaf if we just removed last child of the last of nodes
		 * that wasn't filtered.  We can use one entry because if something was
		 * filtered out, there is space for at least one extra entry. */
		const int parent = j - entry->child_pos;
		const int show_empty_dir_leafs = (cfg.dot_dirs & DD_TREE_LEAFS_PARENT);
		if(show_empty_dir_leafs && remove_subtrees && parent == j - 1 &&
				entries[parent].child_count == 0)
		{
			char full_path[PATH_MAX + 1];
			get_full_path_of(&entries[j - 1], sizeof(full_path), full_path);

			char *path = format_str("%s/..", full_path);
			init_parent_entry(view, &entries[j], path);
			remove_last_path_component(path);
			entries[j].origin = path;
			entries[j].owns_origin = 1;
			entries[j].child_pos = 1;

			/* Since we are now adding back one entry, increase parent counts and
			 * child positions back by one. */
			fix_tree_links(entries, entry, i, j, nremoved, 1);

			++j;
		}

		i += nremoved - 1;
	}

	*count = j;

	if(*count == 0 && !allow_empty_list)
	{
		add_parent_dir(view);
	}

	return i - j;
}

/* Visits all parent nodes to update number of their children and also all
 * following sibling nodes which require their parent links updated.  Can be
 * used while moving entries upward. */
static void
fix_tree_links(dir_entry_t *entries, dir_entry_t *entry, int old_idx,
		int new_idx, int displacement, int correction)
{
	if(entry->child_pos == 0 || correction == 0)
	{
		return;
	}

	displacement += old_idx - new_idx;

	int pos = old_idx + (entry->child_count + 1);
	int parent = new_idx - entry->child_pos;
	while(1)
	{
		while(pos <= parent + displacement + entries[parent].child_count)
		{
			entries[pos].child_pos += correction;
			pos += entries[pos].child_count + 1;
		}
		entries[parent].child_count += correction;
		if(entries[parent].child_pos == 0)
		{
			break;
		}
		parent -= entries[parent].child_pos;
	}
}

/* Checks for subjectively relative size of a directory specified by the path
 * parameter.  Returns non-zero if size of the directory in question is
 * considered to be big. */
static int
is_dir_big(const char path[])
{
#ifndef _WIN32
	struct stat s;
	if(os_stat(path, &s) != 0)
	{
		LOG_SERROR_MSG(errno, "Can't stat() \"%s\"", path);
		return 1;
	}
	return s.st_size > s.st_blksize;
#else
	return 1;
#endif
}

/* Frees list of directory entries of the view. */
static void
free_view_entries(view_t *view)
{
	free_dir_entries(&view->dir_entry, &view->list_rows);
}

/* Updates file list with files from current directory.  Returns zero on
 * success, otherwise non-zero is returned. */
static int
update_dir_list(view_t *view, int reload)
{
	dir_entry_t *prev_dir_entries;
	int prev_list_rows;

	start_dir_list_change(view, &prev_dir_entries, &prev_list_rows, reload);

	if(enum_dir_content(view->curr_dir, &add_file_entry_to_view, view) != 0)
	{
		LOG_SERROR_MSG(errno, "Can't opendir() \"%s\"", view->curr_dir);
		free_dir_entries(&prev_dir_entries, &prev_list_rows);
		return 1;
	}

	if(cfg_parent_dir_is_visible(is_root_dir(view->curr_dir)) ||
			view->list_rows == 0)
	{
		add_parent_dir(view);
	}

	sort_dir_list(!reload, view);

	/* Merging must be performed after sorting so that list position remains fixed
	 * (sorting doesn't preserve it). */
	finish_dir_list_change(view, prev_dir_entries, prev_list_rows);

	return 0;
}

/* Starts file list update, saving previous list for future reference if
 * necessary. */
static void
start_dir_list_change(view_t *view, dir_entry_t **entries, int *len, int reload)
{
	if(reload)
	{
		*entries = view->dir_entry;
		*len = view->list_rows;
		view->dir_entry = NULL;
		view->list_rows = 0;
	}
	else
	{
		*entries = NULL;
		*len = 0;
		free_view_entries(view);
	}

	view->matches = 0;
	view->selected_files = 0;
}

/* Finishes file list update, possibly merging information from old entries into
 * new ones. */
static void
finish_dir_list_change(view_t *view, dir_entry_t *entries, int len)
{
	check_file_uniqueness(view);

	if(entries != NULL)
	{
		merge_lists(view, entries, len);
		free_dir_entries(&entries, &len);
	}

	view->dir_entry = dynarray_shrink(view->dir_entry);
}

/* enum_dir_content() callback that appends files to file list.  Returns zero on
 * success or non-zero to indicate failure and stop enumeration. */
static int
add_file_entry_to_view(const char name[], const void *data, void *param)
{
	view_t *const view = param;
	dir_entry_t *entry;

	/* Always ignore the "." and ".." directories. */
	if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
	{
		return 0;
	}

	if(!entry_is_visible(view, name, data))
	{
		++view->filtered;
		return 0;
	}

	entry = alloc_dir_entry(&view->dir_entry, view->list_rows);
	if(entry == NULL)
	{
		show_error_msg("Memory Error", "Unable to allocate enough memory");
		return 1;
	}

	init_dir_entry(view, entry, name);

	if(fill_dir_entry(entry, entry->name, data) == 0)
	{
		++view->list_rows;
	}
	else
	{
		fentry_free(entry);
	}

	return 0;
}

void
resort_dir_list(int msg, view_t *view)
{
	char full_path[PATH_MAX + 1];
	const int top_delta = view->list_pos - view->top_line;
	if(view->list_pos < view->list_rows)
	{
		get_current_full_path(view, sizeof(full_path), full_path);
	}

	sort_dir_list(msg, view);

	if(view->list_pos < view->list_rows)
	{
		flist_goto_by_path(view, full_path);
		view->top_line = view->list_pos - top_delta;
	}
}

/* Resorts view without reloading it.  msg parameter controls whether to show
 * "Sorting..." status bar message. */
static void
sort_dir_list(int msg, view_t *view)
{
	if(msg && view->list_rows > 2048 && !modes_is_cmdline_like())
	{
		ui_sb_quick_msgf("%s", "Sorting directory...");
	}

	sort_view(view);

	if(msg && !modes_is_cmdline_like())
	{
		ui_sb_clear();
	}
}

/* Merges elements from previous list into the new one. */
static void
merge_lists(view_t *view, dir_entry_t *entries, int len)
{
	int i;
	int closest_dist;
	const int prev_pos = view->list_pos;
	trie_t *prev_names = trie_create(/*free_func=*/NULL);

	for(i = 0; i < len; ++i)
	{
		add_to_trie(prev_names, view, &entries[i]);

		/* We won't use the name later, so free some memory. */
		update_string(&entries[i].name, NULL);
	}

	closest_dist = INT_MIN;
	for(i = 0; i < view->list_rows; ++i)
	{
		int dist;
		void *data;
		dir_entry_t *const entry = &view->dir_entry[i];
		if(!is_in_trie(prev_names, view, entry, &data))
		{
			continue;
		}

		/* Transfer information from previous entry to the new one. */
		merge_entries(entry, data);

		/* Update number of selected files (should have been zeroed beforehand). */
		view->selected_files += (entry->selected != 0);

		/* Update cursor position in a smart way. */
		dist = (dir_entry_t *)data - entries - prev_pos;
		closest_dist = correct_pos(view, i, dist, closest_dist);
	}

	trie_free(prev_names);
}

/* Checks that entries don't have the same name (for non-cv).  And if there are
 * duplicates shows a warning to the user (shown each time broken directory is
 * entered) and drops duplicates. */
TSTATIC void
check_file_uniqueness(view_t *view)
{
	trie_t *file_names = trie_create(/*free_func=*/NULL);

	int had_dups = view->has_dups;

	int i;
	for(i = 0; i < view->list_rows; ++i)
	{
		add_to_trie(file_names, view, &view->dir_entry[i]);
	}

	trie_free(file_names);

	if(view->has_dups)
	{
		(void)exclude_temporary_entries(view);
		if(!had_dups)
		{
			show_error_msg("Broken File System", "Underlying file system seems to "
					"report duplicated file names.  Only one entry will be shown.");
		}
	}
}

/* Adds view entry into the trie mapping its name to entry structure.
 * Duplicated entries of a non-custom view are marked as temporary. */
static void
add_to_trie(trie_t *trie, view_t *view, dir_entry_t *entry)
{
	if(flist_custom_active(view))
	{
		char full_path[PATH_MAX + 1];
		get_full_path_of(entry, sizeof(full_path), full_path);

		int error = trie_set(trie, full_path, entry);
		assert(error == 0 && "Duplicated file names in the list?");
		(void)error;
		return;
	}

	if(trie_set(trie, entry->name, entry) != 0)
	{
		LOG_INFO_MSG("Duplicated entry is `%s` in `%s`", entry->name,
				entry->origin);
		entry->temporary = 1;
		view->has_dups = 1;
	}
}

/* Looks up entry in the trie by its name.  Retrieves directory entry stored by
 * add_to_trie() into *data (unchanged on lookup failure).  Returns non-zero if
 * item was successfully retrieved and zero otherwise. */
static int
is_in_trie(trie_t *trie, view_t *view, dir_entry_t *entry, void **data)
{
	int error;

	if(flist_custom_active(view))
	{
		char full_path[PATH_MAX + 1];
		get_full_path_of(entry, sizeof(full_path), full_path);
		error = trie_get(trie, full_path, data);
	}
	else
	{
		error = trie_get(trie, entry->name, data);
	}

	return (error == 0);
}

/* Merges data from previous entry into the new one.  Both entries should
 * correspond to the same file (their names must match). */
static void
merge_entries(dir_entry_t *new, const dir_entry_t *prev)
{
	new->id = prev->id;

	new->selected = prev->selected;
	new->was_selected = prev->was_selected;
	new->folded = prev->folded;

	/* No need to check for name here, because only entries with exactly the same
	 * names are merged. */
	if(new->type == prev->type)
	{
		new->hi_num = prev->hi_num;
		new->name_dec_num = prev->name_dec_num;
	}
}

/* Corrects selected item position in the list.  Returns updated value of the
 * closest variable, which initially should be INT_MIN. */
static int
correct_pos(view_t *view, int pos, int dist, int closest)
{
	if(dist == 0)
	{
		closest = 0;
		view->list_pos = pos;
	}
	else if((closest < 0 && dist > closest) || (closest > 0 && dist < closest))
	{
		closest = dist;
		view->list_pos = pos;
	}
	return closest;
}

/* Performs actions needed to rescue from abnormal situation with empty
 * filelist.  Returns non-zero if file list was reloaded. */
static int
rescue_from_empty_filelist(view_t *view)
{
	/* It is possible to set the file name filter so that no files are showing
	 * in the / directory.  All other directories will always show at least the
	 * ../ file.  This resets the filter and reloads the directory. */
	if(name_filters_empty(view))
	{
		return 0;
	}

	show_error_msgf("Filter error",
			"The %s\"%s\" pattern did not match any files. It was reset.",
			view->invert ? "" : "inverted ", matcher_get_expr(view->manual_filter));

	name_filters_drop(view);

	load_dir_list(view, 1);
	if(view->list_rows < 1)
	{
		leave_invalid_dir(view);
		(void)change_directory(view, view->curr_dir);
		load_dir_list(view, 0);
	}

	return 1;
}

void
add_parent_dir(view_t *view)
{
	add_parent_entry(view, &view->dir_entry, &view->list_rows);
}

/* Adds parent directory entry (..) to specified list of entries. */
static void
add_parent_entry(view_t *view, dir_entry_t **entries, int *count)
{
	dir_entry_t *const dir_entry = alloc_dir_entry(entries, *count);
	if(dir_entry == NULL)
	{
		show_error_msg("Memory Error", "Unable to allocate enough memory");
		return;
	}

	if(init_parent_entry(view, dir_entry, "..") == 0)
	{
		++*count;
	}
}

/* Initializes dir_entry_t with name and all other fields with default
 * values. */
static void
init_dir_entry(view_t *view, dir_entry_t *entry, const char name[])
{
	entry->name = strdup(name);
	entry->origin = &view->curr_dir[0];

	entry->size = 0ULL;
#ifndef _WIN32
	entry->uid = (uid_t)-1;
	entry->gid = (gid_t)-1;
	entry->mode = (mode_t)0;
	entry->inode = (ino_t)0;
#else
	entry->attrs = 0;
#endif

	entry->mtime = (time_t)0;
	entry->atime = (time_t)0;
	entry->ctime = (time_t)0;

	entry->type = FT_UNK;
	entry->nlinks = 0;
	entry->dir_link = 0;
	entry->slow_target = 0;
	entry->hi_num = -1;
	entry->name_dec_num = -1;

	entry->child_count = 0;
	entry->child_pos = 0;

	/* All files start as unselected, unmatched and unmarked. */
	entry->selected = 0;
	entry->was_selected = 0;
	entry->search_match = 0;
	entry->marked = 0;
	entry->temporary = 0;
	entry->owns_origin = 0;
	entry->folded = 0;

	entry->tag = -1;
	entry->id = -1;
	entry->link = -1;
}

void
replace_dir_entries(view_t *view, dir_entry_t **entries, int *count,
		const dir_entry_t *with_entries, int with_count)
{
	dir_entry_t *new;
	int i;

	new = dynarray_extend(NULL, with_count*sizeof(*new));
	if(new == NULL)
	{
		return;
	}

	memcpy(new, with_entries, sizeof(*new)*with_count);

	for(i = 0; i < with_count; ++i)
	{
		dir_entry_t *const entry = &new[i];

		entry->name = strdup(entry->name);
		entry->origin = strdup(entry->origin);
		entry->owns_origin = 1;

		if(entry->name == NULL || entry->origin == NULL)
		{
			int count_so_far = i + 1;
			free_dir_entries(&new, &count_so_far);
			return;
		}
	}

	free_dir_entries(entries, count);
	*entries = new;
	*count = with_count;
}

void
free_dir_entries(dir_entry_t **entries, int *count)
{
	int i;
	for(i = 0; i < *count; ++i)
	{
		fentry_free(&(*entries)[i]);
	}

	dynarray_free(*entries);
	*entries = NULL;
	*count = 0;
}

void
fentry_free(dir_entry_t *entry)
{
	free(entry->name);
	entry->name = NULL;

	if(entry->owns_origin)
	{
		free(entry->origin);
		entry->origin = NULL;
	}
}

dir_entry_t *
add_dir_entry(dir_entry_t **list, size_t *list_size, const dir_entry_t *entry)
{
	dir_entry_t *const new_entry = alloc_dir_entry(list, *list_size);
	if(new_entry == NULL)
	{
		return NULL;
	}

	*new_entry = *entry;
	++*list_size;
	return new_entry;
}

dir_entry_t *
entry_list_add(view_t *view, dir_entry_t **list, int *list_size,
		const char path[])
{
	dir_entry_t *const dir_entry = alloc_dir_entry(list, *list_size);
	if(dir_entry == NULL)
	{
		return NULL;
	}

	init_dir_entry(view, dir_entry, get_last_path_component(path));

	dir_entry->origin = strdup(path);
	dir_entry->owns_origin = 1;
	remove_last_path_component(dir_entry->origin);

	if(fill_dir_entry_by_path(dir_entry, path) != 0)
	{
		fentry_free(dir_entry);
		return NULL;
	}

	++*list_size;
	return dir_entry;
}

/* Allocates one more directory entry for the *list of size list_size by
 * extending it.  Returns pointer to new entry or NULL on failure. */
static dir_entry_t *
alloc_dir_entry(dir_entry_t **list, int list_size)
{
	dir_entry_t *new_entry_list = dynarray_extend(*list, sizeof(dir_entry_t));
	if(new_entry_list == NULL)
	{
		return NULL;
	}

	*list = new_entry_list;
	return &new_entry_list[list_size];
}

void
check_if_filelist_has_changed(view_t *view)
{
	int failed, changed;
	const char *const curr_dir = flist_get_dir(view);

	if(view->on_slow_fs ||
			(flist_custom_active(view) && !cv_tree(view->custom.type)) ||
			is_unc_root(curr_dir))
	{
		return;
	}

	if(view->watch == NULL)
	{
		/* If watch is not initialized, try to do this, but don't fail on error. */

		update_dir_watcher(view);
		failed = 0;
		changed = (view->watch != NULL);
	}
	else
	{
		FSWatchState state = poll_watcher(view->watch, curr_dir);
		changed = (state != FSWS_UNCHANGED);
		failed = (state == FSWS_ERRORED);
	}

	/* Check if we still have permission to visit this directory. */
	if(os_access(curr_dir, X_OK) != 0)
	{
		LOG_SERROR_MSG(errno, "Can't access(X_OK) \"%s\"", curr_dir);
		log_cwd();
		failed = 1;
	}

	if(failed)
	{
		show_error_msgf("Directory Check", "Cannot open %s", curr_dir);

		leave_invalid_dir(view);
		(void)change_directory(view, curr_dir);
		flist_sel_stash(view);
		ui_view_schedule_reload(view);
		return;
	}

	if(changed)
	{
		ui_view_schedule_reload(view);
	}
	else if(flist_custom_active(view) && cv_tree(view->custom.type))
	{
		if(flist_is_fs_backed(view) &&
				tree_has_changed(view->dir_entry, view->list_rows))
		{
			ui_view_schedule_reload(view);
		}
	}
	else
	{
		if(flist_update_cache(view, &view->left_column, view->left_column.dir) ||
				flist_update_cache(view, &view->right_column, view->right_column.dir))
		{
			ui_view_schedule_redraw(view);
		}
	}
}

/* Checks whether tree-view needs a reload (any of subdirectories were changed).
 * Returns non-zero if so, otherwise zero is returned. */
static int
tree_has_changed(const dir_entry_t *entries, size_t nchildren)
{
	size_t pos = 0U;
	while(pos < nchildren)
	{
		const dir_entry_t *const entry = &entries[pos];
		if(entry->type == FT_DIR && !is_parent_dir(entry->name))
		{
			char full_path[PATH_MAX + 1];
			struct stat s;

			get_full_path_of(entry, sizeof(full_path), full_path);
			if(os_stat(full_path, &s) != 0 || entry->mtime != s.st_mtime)
			{
				return 1;
			}

			if(tree_has_changed(entry + 1, entry->child_count))
			{
				return 1;
			}
		}

		pos += entry->child_count + 1;
	}
	return 0;
}

int
flist_update_cache(view_t *view, cached_entries_t *cache, const char path[])
{
	int update = 0;

	if(path == NULL)
	{
		return 0;
	}

	if(cache->watch == NULL || stroscmp(cache->dir, path) != 0)
	{
		fswatch_free(cache->watch);

		cache->watch = fswatch_create(path);
		if(cache->watch == NULL)
		{
			/* Reset the cache on failure to create a watcher to do not accidentally
			 * provide incorrect data. */
			flist_free_cache(cache);
			return 0;
		}

		replace_string(&cache->dir, path);

		update = 1;
	}

	if(poll_watcher(cache->watch, path) != FSWS_UNCHANGED || update)
	{
		free_dir_entries(&cache->entries.entries, &cache->entries.nentries);
		cache->entries = flist_list_in(view, path, 0, 1);
		return 1;
	}

	return 0;
}

/* Polls file-system watcher and re-enters current working directory of the
 * process if necessary.  Returns watcher's state. */
static FSWatchState
poll_watcher(fswatch_t *watch, const char path[])
{
	FSWatchState state = fswatch_poll(watch);

	if(state == FSWS_ERRORED || state == FSWS_REPLACED)
	{
		char curr_path[PATH_MAX + 1];
		if(get_cwd(curr_path, sizeof(curr_path)) == curr_path)
		{
			if(stroscmp(curr_path, path) == 0)
			{
				/* Re-enter current directory (vifm_chdir() can skip system call thus
				 * not synchronizing location with updated file-system data). */
				(void)os_chdir(path);
			}
		}
	}

	return state;
}

void
flist_free_cache(cached_entries_t *cache)
{
	free_dir_entries(&cache->entries.entries, &cache->entries.nentries);
	update_string(&cache->dir, NULL);
	fswatch_free(cache->watch);
	cache->watch = NULL;
}

void
flist_update_origins(view_t *view)
{
	int i;
	for(i = 0; i < view->list_rows; ++i)
	{
		dir_entry_t *const entry = &view->dir_entry[i];
		if(!entry->owns_origin)
		{
			entry->origin = &view->curr_dir[0];
		}
	}
}

void
flist_toggle_fold(view_t *view)
{
	if(!flist_custom_active(view) || !cv_tree(view->custom.type))
	{
		return;
	}

	dir_entry_t *curr = get_current_entry(view);
	if(curr->type != FT_DIR)
	{
		return;
	}

	char full_path[PATH_MAX + 1];
	get_full_path_of(curr, sizeof(full_path), full_path);

	FoldState state = get_fold_state(view->custom.folded_paths, full_path);

	assert(curr->folded == (state == FOLD_USER_CLOSED ||
				state == FOLD_AUTO_CLOSED) && "Metadata is not in sync.");

	switch(state)
	{
		case FOLD_UNDEFINED:   state = FOLD_USER_CLOSED; break;
		case FOLD_USER_OPENED: state = FOLD_USER_CLOSED; break;
		case FOLD_USER_CLOSED: state = FOLD_USER_OPENED; break;
		case FOLD_AUTO_OPENED: state = FOLD_USER_CLOSED; break;
		case FOLD_AUTO_CLOSED: state = FOLD_AUTO_OPENED; break;
	}

	if(set_fold_state(view->custom.folded_paths, full_path, state))
	{
		curr->folded = !curr->folded;
		/* We reload even on folding to update number of filtered entries
		 * properly. */
		ui_view_schedule_reload(view);
	}
}

/* Folds a single entry by removing all of its children and updating tree
 * metadata accordingly. */
static void
remove_child_entries(view_t *view, dir_entry_t *entry)
{
	const int pos = (entry - view->dir_entry);
	const int child_count = entry->child_count;

	flist_custom_save(view);

	int i;
	for(i = 0; i < child_count; ++i)
	{
		fentry_free(&entry[1 + i]);
	}

	fix_tree_links(view->dir_entry, entry, pos, pos, 0, -child_count);

	memmove(entry + 1, entry + 1 + child_count,
			sizeof(*entry)*(view->list_rows - (pos + 1 + child_count)));
	view->list_rows -= child_count;
	entry->child_count = 0;
}

int
cd_is_possible(const char path[])
{
	if(!is_valid_dir(path))
	{
		LOG_SERROR_MSG(errno, "Can't access \"%s\"", path);

		show_error_msgf("Destination doesn't exist or isn't a directory", "\"%s\"",
				path);
		return 0;
	}
	else if(!directory_accessible(path))
	{
		LOG_SERROR_MSG(errno, "Can't access(, X_OK) \"%s\"", path);

		show_error_msgf("Permission denied", "\"%s\"", path);
		return 0;
	}
	else
	{
		return 1;
	}
}

void
load_saving_pos(view_t *view)
{
	char full_path[PATH_MAX + 1];

	if(curr_stats.load_stage < 2)
		return;

	if(!window_shows_dirlist(view))
		return;

	if(view->local_filter.in_progress)
	{
		return;
	}

	get_current_full_path(view, sizeof(full_path), full_path);

	load_dir_list_internal(view, 1, 1);

	flist_goto_by_path(view, full_path);

	fview_cursor_redraw(view);

	if(ui_view_is_visible(view))
	{
		refresh_view_win(view);
	}
}

int
window_shows_dirlist(const view_t *view)
{
	if(!ui_view_is_visible(view))
	{
		return 0;
	}

	if(view->explore_mode)
	{
		return 0;
	}

	if(view == other_view && vle_mode_is(VIEW_MODE))
	{
		return 0;
	}

	if(view == other_view && curr_stats.preview.on)
	{
		return 0;
	}

	if(NONE(vle_primary_mode_is, NORMAL_MODE, VISUAL_MODE, VIEW_MODE,
				CMDLINE_MODE))
	{
		return 0;
	}

	return 1;
}

void
change_sort_type(view_t *view, char type, char descending)
{
	if(cv_compare(view->custom.type))
	{
		return;
	}

	view->sort[0] = descending ? -type : type;
	memset(&view->sort[1], SK_NONE, sizeof(view->sort) - 1);
	memcpy(&view->sort_g[0], &view->sort[0], sizeof(view->sort_g));

	fview_sorting_updated(view);

	load_sort_option(view);

	ui_view_schedule_reload(view);
}

int
pane_in_dir(const view_t *view, const char path[])
{
	return paths_are_same(view->curr_dir, path);
}

int
cd(view_t *view, const char base_dir[], const char path[])
{
	char dir[PATH_MAX + 1];
	int updir;

	flist_pick_cd_path(view, base_dir, path, &updir, dir, sizeof(dir));

	if(updir)
	{
		rn_leave(view, 1);
	}
	else
	{
		char canonic_dir[PATH_MAX + 1];
		to_canonic_path(dir, base_dir, canonic_dir, sizeof(canonic_dir));
		if(!cd_is_possible(canonic_dir) || change_directory(view, canonic_dir) < 0)
		{
			return 0;
		}
	}

	load_dir_list(view, 0);
	if(view == curr_view)
	{
		fview_cursor_redraw(view);
	}
	else
	{
		draw_dir_list(other_view);
		refresh_view_win(other_view);
	}
	return 0;
}

void
flist_pick_cd_path(view_t *view, const char base_dir[], const char path[],
		int *updir, char buf[], size_t buf_size)
{
	char *arg;

	*updir = 0;

	if(is_null_or_empty(path))
	{
		copy_str(buf, buf_size, cfg.home_dir);
		return;
	}

	arg = expand_tilde(path);

#ifndef _WIN32
	if(is_path_absolute(arg))
		copy_str(buf, buf_size, arg);
#else
	if(is_path_absolute(arg) && *arg != '/')
		copy_str(buf, buf_size, arg);
	else if(*arg == '/' && is_unc_root(arg))
		copy_str(buf, buf_size, arg);
	else if(*arg == '/' && is_unc_path(arg))
		copy_str(buf, buf_size, arg);
	else if(*arg == '/' && is_unc_path(base_dir))
		sprintf(buf + strlen(buf), "/%s", arg + 1);
	else if(strcmp(arg, "/") == 0 && is_unc_path(base_dir))
		copy_str(buf, strchr(base_dir + 2, '/') - base_dir + 1, base_dir);
	else if(*arg == '/')
		snprintf(buf, buf_size, "%c:%s", base_dir[0], arg);
#endif
	else if(strcmp(arg, "-") == 0)
		copy_str(buf, buf_size, view->last_dir == NULL ? "." : view->last_dir);
	else if(is_parent_dir(arg) && stroscmp(base_dir, flist_get_dir(view)) == 0)
		*updir = 1;
	else
		find_dir_in_cdpath(base_dir, arg, buf, buf_size);
	free(arg);
}

/* Searches for an existing directory in cdpath and fills the buf with final
 * absolute path. */
static void
find_dir_in_cdpath(const char base_dir[], const char dst[], char buf[],
		size_t buf_size)
{
	char *free_this;
	char *part, *state;

	if(is_builtin_dir(dst) || starts_with_lit(dst, "./") ||
			starts_with_lit(dst, "../"))
	{
		snprintf(buf, buf_size, "%s/%s", base_dir, dst);
		return;
	}

	part = strdup(cfg.cd_path);
	free_this = part;

	state = NULL;
	while((part = split_and_get(part, ',', &state)) != NULL)
	{
		char *expanded = expand_tilde(part);

		build_path(buf, buf_size, expanded, dst);
		free(expanded);

		if(is_dir(buf))
		{
			free(free_this);
			return;
		}
	}
	free(free_this);

	snprintf(buf, buf_size, "%s/%s", base_dir, dst);
}

int
go_to_sibling_dir(view_t *view, int offset, int wrap)
{
	entries_t parent_dirs;
	dir_entry_t *entry;
	int save_msg = 0;

	if(is_root_dir(flist_get_dir(view)) || flist_custom_active(view))
	{
		return 0;
	}

	parent_dirs = list_sibling_dirs(view);
	if(parent_dirs.nentries < 0)
	{
		show_error_msg("Error", "Can't list parent directory");
		return 0;
	}

	sort_entries(view, parent_dirs);

	entry = pick_sibling(view, parent_dirs, offset, wrap, &save_msg);
	if(entry != NULL)
	{
		char full_path[PATH_MAX + 1];
		get_full_path_of(entry, sizeof(full_path), full_path);
		if(change_directory(view, full_path) >= 0)
		{
			load_dir_list(view, 0);
			fview_position_updated(view);
		}
	}

	free_dir_entries(&parent_dirs.entries, &parent_dirs.nentries);
	return save_msg;
}

/* Lists siblings of current directory of the view (includes current directory
 * as well).  Returns the list, which is of length -1 on error. */
static entries_t
list_sibling_dirs(view_t *view)
{
	entries_t parent_dirs = {};
	char *path;

	if(is_root_dir(flist_get_dir(view)))
	{
		parent_dirs.nentries = -1;
		return parent_dirs;
	}

	path = strdup(flist_get_dir(view));
	remove_last_path_component(path);

	parent_dirs = flist_list_in(view, path, 1, 0);

	free(path);

	if(parent_dirs.nentries < 0)
	{
		return parent_dirs;
	}

	if(entry_from_path(view, parent_dirs.entries, parent_dirs.nentries,
				flist_get_dir(view)) == NULL)
	{
		/* If we couldn't find our current directory in the list (because it got
		 * filtered-out), add it to be able to determine where it would go if it
		 * were visible.  Failure to add it is implicitly handled on checking
		 * result of pick_sibling(). */
		(void)entry_list_add(view, &parent_dirs.entries, &parent_dirs.nentries,
				flist_get_dir(view));
	}

	return parent_dirs;
}

/* Lists files of specified directory.  Returns the list, which is of length -1
 * on error. */
static entries_t
flist_list_in(view_t *view, const char path[], int only_dirs,
		int can_include_parent)
{
	entries_t siblings = {};
	int len, i;
	char **list;

	list = list_all_files(path, &len);
	if(len < 0)
	{
		siblings.nentries = -1;
		return siblings;
	}

	for(i = 0; i < len; ++i)
	{
		dir_entry_t *entry;
		int is_dir;

		if(view->hide_dot && list[i][0] == '.')
		{
			continue;
		}

		char *full_path = format_str("%s/%s", path, list[i]);
		entry = entry_list_add(view, &siblings.entries, &siblings.nentries,
				full_path);
		free(full_path);

		if(entry == NULL)
		{
			continue;
		}

		is_dir = fentry_is_dir(entry);
		if((only_dirs && !is_dir) ||
				!filters_file_is_visible(view, path, list[i], is_dir, 0))
		{
			fentry_free(entry);
			--siblings.nentries;
		}
	}
	free_string_array(list, len);

	if(can_include_parent && cfg_parent_dir_is_visible(is_root_dir(path)))
	{
		char *const full_path = format_str("%s/..", path);
		if(full_path != NULL)
		{
			/* Failure to add parent directory entry is by no means critical. */
			(void)entry_list_add(view, &siblings.entries, &siblings.nentries,
					full_path);
			free(full_path);
		}
	}

	return siblings;
}

/* Picks next or previous sibling from the list with optional wrapping.
 * *wrapped is set to non-zero if wrapping happened.  Returns pointer to picked
 * sibling or NULL on error or if can't pick. */
static dir_entry_t *
pick_sibling(view_t *view, entries_t parent_dirs, int offset, int wrap,
		int *wrapped)
{
	dir_entry_t *entry;
	int i, initial;

	/* Find parent entry. */
	entry = entry_from_path(view, parent_dirs.entries, parent_dirs.nentries,
			flist_get_dir(view));
	if(entry == NULL)
	{
		show_error_msg("Error", "Can't find current directory position");
		return NULL;
	}

	*wrapped = 0;
	initial = entry - parent_dirs.entries;
	i = initial;

	while(offset != 0)
	{
		if(offset > 0 && i < parent_dirs.nentries - 1)
		{
			++i;
		}
		else if(offset < 0 && i > 0)
		{
			--i;
		}
		else if(wrap)
		{
			if(offset > 0)
			{
				i = 0;
				ui_sb_err("Hit BOTTOM, continuing at TOP");
			}
			else
			{
				i = parent_dirs.nentries - 1;
				ui_sb_err("Hit TOP, continuing at BOTTOM");
			}
			*wrapped = 1;
		}
		else
		{
			return (i == initial ? NULL : entry);
		}

		entry = &parent_dirs.entries[i];
		offset = (offset > 0 ? offset - 1 : offset + 1);
	}

	return entry;
}

int
view_needs_cd(const view_t *view, const char path[])
{
	if(path[0] == '\0' || stroscmp(view->curr_dir, path) == 0)
	{
		return 0;
	}
	return 1;
}

void
set_view_path(view_t *view, const char path[])
{
	if(view_needs_cd(view, path))
	{
		copy_str(view->curr_dir, sizeof(view->curr_dir), path);
		exclude_file_name(view->curr_dir);
	}
}

uint64_t
fentry_get_size(const view_t *view, const dir_entry_t *entry)
{
	uint64_t size = DCACHE_UNKNOWN;

	if(fentry_is_dir(entry))
	{
		fentry_get_dir_info(view, entry, &size, NULL);
	}

	return (size == DCACHE_UNKNOWN ? entry->size : size);
}

int
iter_selected_entries(view_t *view, dir_entry_t **entry)
{
	return iter_entries(view, entry, &is_entry_selected, /*valid_only=*/1);
}

int
iter_marked_entries(view_t *view, dir_entry_t **entry)
{
	return iter_entries(view, entry, &is_entry_marked, /*valid_only=*/1);
}

/* Implements iteration over the entries which match specific predicate.
 * Returns non-zero if matching entry is found and is loaded to *entry,
 * otherwise it's set to NULL and zero is returned. */
static int
iter_entries(view_t *view, dir_entry_t **entry, entry_predicate pred,
		int valid_only)
{
	int next = (*entry == NULL) ? 0 : (*entry - view->dir_entry + 1);

	while(next < view->list_rows)
	{
		dir_entry_t *const e = &view->dir_entry[next];
		if((!valid_only || fentry_is_valid(e)) && pred(e))
		{
			*entry = e;
			return 1;
		}
		++next;
	}

	*entry = NULL;
	return 0;
}

int
is_entry_selected(const dir_entry_t *entry)
{
	return entry->selected;
}

int
is_entry_marked(const dir_entry_t *entry)
{
	return entry->marked;
}

int
iter_selection_or_current(view_t *view, dir_entry_t **entry)
{
	if(view->selected_files == 0)
	{
		dir_entry_t *const curr = get_current_entry(view);
		*entry = (*entry == NULL && fentry_is_valid(curr)) ? curr : NULL;
		return *entry != NULL;
	}
	return iter_selected_entries(view, entry);
}

int
iter_selection_or_current_any(view_t *view, dir_entry_t **entry)
{
	if(view->selected_files == 0)
	{
		dir_entry_t *const curr = get_current_entry(view);
		*entry = (*entry == NULL ? curr : NULL);
		return *entry != NULL;
	}
	return iter_entries(view, entry, &is_entry_selected, /*valid_only=*/0);
}

int
entry_to_pos(const view_t *view, const dir_entry_t *entry)
{
	const int pos = entry - view->dir_entry;
	return (pos >= 0 && pos < view->list_rows) ? pos : -1;
}

void
get_current_full_path(const view_t *view, size_t buf_len, char buf[])
{
	get_full_path_at(view, view->list_pos, buf_len, buf);
}

void
get_full_path_at(const view_t *view, int pos, size_t buf_len, char buf[])
{
	if(pos < 0 || pos >= view->list_rows)
	{
		copy_str(buf, buf_len, "");
		return;
	}
	get_full_path_of(&view->dir_entry[pos], buf_len, buf);
}

void
get_full_path_of(const dir_entry_t *entry, size_t buf_len, char buf[])
{
	build_path(buf, buf_len, entry->origin, entry->name);
}

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[])
{
	char name[NAME_MAX + 1];
	const char *path = entry->origin;

	char parent_path[PATH_MAX + 1];
	const char *root_path = flist_get_dir(view);

	if(fentry_is_fake(entry))
	{
		copy_str(buf, buf_len, "");
		return;
	}

	if(drop_prefix && entry->child_pos != 0)
	{
		/* Replace root to force obtaining of file name only. */
		const dir_entry_t *const parent_entry = entry - entry->child_pos;
		get_full_path_of(parent_entry, sizeof(parent_path), parent_path);
		root_path = parent_path;
	}

	format_entry_name(entry, fmt, sizeof(name), name);

	if(is_parent_dir(entry->name) || is_root_dir(entry->name))
	{
		copy_str(buf, buf_len, name);
		return;
	}

	if(!path_starts_with(path, root_path))
	{
		build_path(buf, buf_len, path, name);
		return;
	}

	assert(strlen(path) >= strlen(root_path) && "Path is too short.");

	path += strlen(root_path);
	path = skip_char(path, '/');
	if(path[0] == '\0')
	{
		copy_str(buf, buf_len, name);
	}
	else
	{
		snprintf(buf, buf_len, "%s/%s", path, name);
	}
}

void
check_marking(view_t *view, int count, const int indexes[])
{
	if(count != 0)
	{
		mark_files_at(view, count, indexes);
	}
	else
	{
		flist_set_marking(view, 0);
	}
}

void
flist_set_marking(view_t *view, int prefer_current)
{
	if(view->pending_marking)
	{
		view->pending_marking = 0;
		return;
	}

	dir_entry_t *curr = get_current_entry(view);
	if(view->selected_files != 0 &&
			(!prefer_current || (curr != NULL && curr->selected)))
	{
		mark_selected(view);
	}
	else
	{
		clear_marking(view);

		if(curr != NULL && fentry_is_valid(curr))
		{
			curr->marked = 1;
		}
	}
}

void
flist_mark_count(view_t *view, int at, int count)
{
	/* Use current position if none given. */
	if(at < 0)
	{
		at = view->list_pos;
	}

	clear_marking(view);

	while(count-- > 0 && at < view->list_rows)
	{
		if(fentry_is_valid(&view->dir_entry[at]))
		{
			view->dir_entry[at].marked = 1;
			++view->selected_files;
		}
		++at;
	}
}

void
mark_files_at(view_t *view, int count, const int indexes[])
{
	clear_marking(view);

	int i;
	for(i = 0; i < count; ++i)
	{
		view->dir_entry[indexes[i]].marked = 1;
	}

	view->pending_marking = 1;
}

/* Marks selected files of the view.  Returns number of marked files. */
static int
mark_selected(view_t *view)
{
	int i;
	int nmarked = 0;
	for(i = 0; i < view->list_rows; ++i)
	{
		view->dir_entry[i].marked = view->dir_entry[i].selected;
		if(view->dir_entry[i].marked)
		{
			++nmarked;
		}
	}
	return nmarked;
}

int
mark_selection_or_current(view_t *view)
{
	dir_entry_t *const curr = get_current_entry(view);
	if(view->selected_files == 0 && fentry_is_valid(curr))
	{
		clear_marking(view);
		curr->marked = 1;
		return 1;
	}
	return mark_selected(view);
}

int
flist_count_marked(const view_t *view)
{
	int i, n = 0;
	for(i = 0; i < view->list_rows; ++i)
	{
		n += (view->dir_entry[i].marked != 0);
	}
	return n;
}

void
clear_marking(view_t *view)
{
	int i;
	for(i = 0; i < view->list_rows; ++i)
	{
		view->dir_entry[i].marked = 0;
	}
}

void
flist_custom_set(view_t *view, const char title[], const char path[],
		char *lines[], int nlines)
{
	int i;

	if(vifm_chdir(path) != 0)
	{
		show_error_msgf("Custom view", "Can't change directory: %s", path);
		return;
	}

	flist_custom_start(view, "-");

	for(i = 0; i < nlines; ++i)
	{
		flist_custom_add_spec(view, lines[i]);
	}

	flist_custom_end(view, 1);
}

void
flist_custom_add_spec(view_t *view, const char line[])
{
	char *const path = parse_line_for_path(line, flist_get_dir(view));
	if(path != NULL)
	{
		flist_custom_add(view, path);
		free(path);
	}
}

void
flist_custom_end(view_t *view, int very)
{
	if(flist_custom_finish(view, very ? CV_VERY : CV_REGULAR, 0) != 0)
	{
		show_error_msg("Custom view", "Ignoring empty list of files");
		return;
	}

	fpos_set_pos(view, 0);
}

void
fentry_rename(view_t *view, dir_entry_t *entry, const char to[])
{
	char *const old_name = entry->name;

	/* Rename file in internal structures for correct positioning of cursor
	 * after reloading, as cursor will be positioned on the file with the same
	 * name. */
	entry->name = strdup(to);
	if(entry->name == NULL)
	{
		entry->name = old_name;
		return;
	}

	/* Name change can affect name specific highlight and decorations, so reset
	 * the caches. */
	entry->hi_num = -1;
	entry->name_dec_num = -1;

	/* Update origins of entries which include the one we're renaming. */
	if(flist_custom_active(view) && fentry_is_dir(entry))
	{
		int i;
		char *const root = format_str("%s/%s", entry->origin, old_name);
		const size_t root_len = strlen(root);

		/* TODO: this doesn't handle circular renames like a/ -> b/, needs more
		 *       bookkeeping. */
		for(i = 0; i < view->list_rows; ++i)
		{
			dir_entry_t *const e = &view->dir_entry[i];
			if(path_starts_with(e->origin, root))
			{
				char full_path[PATH_MAX + 1];
				get_full_path_of(e, sizeof(full_path), full_path);
				FoldState state = get_fold_state(view->custom.folded_paths, full_path);

				char *const new_origin = format_str("%s/%s%s", entry->origin, to,
						e->origin + root_len);
				chosp(new_origin);
				if(e->owns_origin)
				{
					free(e->origin);
				}
				e->origin = new_origin;
				e->owns_origin = 1;

				/* Clone visible child folds. */
				e->folded = 0;
				get_full_path_of(e, sizeof(full_path), full_path);
				if(set_fold_state(view->custom.folded_paths, full_path, state))
				{
					e->folded = (state == FOLD_USER_CLOSED || state == FOLD_AUTO_CLOSED);
				}
			}
		}

		free(root);
	}

	/* Cloning of a folded directory should produce a folded clone. */
	if(entry->folded)
	{
		char full_path[PATH_MAX + 1];
		build_path(full_path, sizeof(full_path), entry->origin, old_name);

		FoldState state = get_fold_state(view->custom.folded_paths, full_path);

		get_full_path_of(entry, sizeof(full_path), full_path);
		if(!set_fold_state(view->custom.folded_paths, full_path, state))
		{
			entry->folded = 0;
		}
	}

	free(old_name);
}

int
fentry_is_fake(const dir_entry_t *entry)
{
	return entry->name[0] == '\0';
}

int
fentry_is_valid(const dir_entry_t *entry)
{
	return !fentry_is_fake(entry) && !is_parent_dir(entry->name);
}

int
fentry_is_dir(const dir_entry_t *entry)
{
	return (entry->type == FT_LINK) ? entry->dir_link : (entry->type == FT_DIR);
}

int
fentry_points_to(const dir_entry_t *entry, const char path[])
{
	char previewed[PATH_MAX + 1];
	get_full_path_of(entry, sizeof(previewed), previewed);

	if(paths_are_equal(path, previewed))
	{
		return 1;
	}

	/* Can also check resolved paths for symbolic links. */
	if(entry->type != FT_LINK)
	{
		/* Nothing more to do for a non-link. */
		return 0;
	}

	if(get_link_target_abs(previewed, entry->origin, previewed,
				sizeof(previewed)) != 0)
	{
		/* We don't know. */
		return 0;
	}
	return paths_are_equal(path, previewed);
}

int
flist_load_tree(view_t *view, const char path[], int depth)
{
	char full_path[PATH_MAX + 1];
	get_current_full_path(view, sizeof(full_path), full_path);

	if(flist_load_tree_internal(view, path, 0, depth) != 0)
	{
		return 1;
	}

	if(full_path[0] != '\0')
	{
		(void)set_position_by_path(view, full_path);
	}

	ui_view_schedule_redraw(view);
	return 0;
}

/* Looks up entry by its path in main entry list of the view and updates cursor
 * position when such entry is found.  Returns zero if position was updated,
 * otherwise non-zero is returned. */
static int
set_position_by_path(view_t *view, const char path[])
{
	const dir_entry_t *entry;
	entry = entry_from_path(view, view->dir_entry, view->list_rows, path);
	if(entry != NULL)
	{
		view->list_pos = entry_to_pos(view, entry);
		return 0;
	}
	return 1;
}

int
flist_clone_tree(view_t *to, const view_t *from)
{
	if(from->custom.type == CV_CUSTOM_TREE)
	{
		const int as_tree = (from->custom.type == CV_CUSTOM_TREE);
		flist_custom_clone(to, from, as_tree);
		re_apply_folds(to, from->custom.folded_paths);
	}
	else
	{
		if(make_tree(to, flist_get_dir(from), 0, from->custom.excluded_paths,
					from->custom.folded_paths, INT_MAX) != 0)
		{
			return 1;
		}
	}

	trie_free(to->custom.excluded_paths);
	to->custom.excluded_paths = trie_clone(from->custom.excluded_paths);

	trie_free(to->custom.folded_paths);
	to->custom.folded_paths = trie_clone(from->custom.folded_paths);

	return 0;
}

/* Implements tree view (re)loading.  The depth parameter can be used to limit
 * nesting level (>= 0).  Returns zero on success, otherwise
 * non-zero is returned. */
static int
flist_load_tree_internal(view_t *view, const char path[], int reload, int depth)
{
	trie_t *excluded_paths = reload ? view->custom.excluded_paths : NULL;
	trie_t *folded_paths = reload ? view->custom.folded_paths
	                              : trie_create(/*free_func=*/NULL);
	if(make_tree(view, path, reload, excluded_paths, folded_paths, depth) != 0)
	{
		if(!reload)
		{
			trie_free(folded_paths);
		}
		return 1;
	}

	if(!reload)
	{
		trie_free(view->custom.excluded_paths);
		view->custom.excluded_paths = trie_create(/*free_func=*/NULL);

		trie_free(view->custom.folded_paths);
		view->custom.folded_paths = folded_paths;
	}

	return 0;
}

/* (Re)loads tree at path into the view using specified list of excluded files.
 * The depth parameter can be used to limit nesting level (>= 0).  Returns zero
 * on success, otherwise non-zero is returned. */
static int
make_tree(view_t *view, const char path[], int reload, trie_t *excluded_paths,
		trie_t *folded_paths, int depth)
{
	char canonic_path[PATH_MAX + 1];
	int nfiltered;
	CVType type;
	const int from_custom = flist_custom_active(view)
	                     && ONE_OF(view->custom.type, CV_REGULAR, CV_VERY);

	flist_custom_start(view, from_custom ? view->custom.title : "");

	show_progress("Building tree...", 0);

	ui_cancellation_push_on();
	if(from_custom)
	{
		nfiltered = 0;
		tree_from_cv(view);
		type = CV_CUSTOM_TREE;
	}
	else
	{
		nfiltered = add_files_recursively(view, path, excluded_paths, folded_paths,
				-1, 0, depth);
		type = CV_TREE;
	}
	ui_cancellation_pop();

	ui_sb_quick_msg_clear();

	if(ui_cancellation_requested())
	{
		return 1;
	}

	if(nfiltered < 0)
	{
		show_error_msg("Tree View", "Failed to list directory");
		return 1;
	}

	to_canonic_path(path, flist_get_dir(view), canonic_path,
			sizeof(canonic_path));

	if(flist_custom_finish_internal(view, type, reload, canonic_path, 1) != 0)
	{
		return 1;
	}
	view->filtered = nfiltered;

	replace_string(&view->custom.orig_dir, canonic_path);

	return 0;
}

/* Turns custom list into custom tree. */
static void
tree_from_cv(view_t *view)
{
	int i;

	dir_entry_t *entries = view->dir_entry;
	int nentries = view->list_rows;

	fsdata_t *const tree = fsdata_create(0, 0);

	view->dir_entry = NULL;
	view->list_rows = 0;

	for(i = 0U; i < nentries; ++i)
	{
		if(!is_parent_dir(entries[i].name))
		{
			char full_path[PATH_MAX + 1];
			void *data = &entries[i];
			get_full_path_of(&entries[i], sizeof(full_path), full_path);
			fsdata_set(tree, full_path, &data, sizeof(data));

			/* Mark entries that came from original list. */
			entries[i].tag = 1;
		}
		else
		{
			fentry_free(&entries[i]);
		}
	}

	if(fsdata_traverse(tree, &complete_tree, view) != 0)
	{
		reset_entry_list(view, &view->custom.entries, &view->custom.entry_count);
	}

	fsdata_free(tree);
	dynarray_free(entries);

	drop_tops(view->custom.entries, &view->custom.entry_count, 1);
}

/* fsdata_traverse() callback that flattens the tree into array of entries.
 * Should return non-zero to stop traverser. */
static int
complete_tree(const char name[], int valid, const void *parent_data, void *data,
		void *arg)
{
	/* Initially data associated with existing entries points to original entries.
	 * Once that entry is copied, the data is replaced with its index in the new
	 * list. */

	view_t *const view = arg;
	const int in_place = cv_tree(view->custom.type);

	dir_entry_t **entries = (in_place ? &view->dir_entry : &view->custom.entries);
	int *nentries = (in_place ? &view->list_rows : &view->custom.entry_count);

	dir_entry_t *dir_entry = alloc_dir_entry(entries, *nentries);
	if(dir_entry == NULL)
	{
		return 1;
	}

	++*nentries;

	if(valid)
	{
		*dir_entry = **(dir_entry_t **)data;
		dir_entry->child_count = 0;
		(*(dir_entry_t **)data)->name = NULL;
		(*(dir_entry_t **)data)->origin = NULL;
	}
	else
	{
		char full_path[PATH_MAX + 1];

		if(parent_data == NULL)
		{
			char *const typed_path = format_str("%s/", name);
			if(is_root_dir(typed_path))
			{
				/* Entry for a root is added temporarily (this happens only on Windows)
				 * as a storage of path prefix and is removed afterwards in
				 * drop_tops(). */
				init_dir_entry(view, dir_entry, "");
				dir_entry->origin = strdup(name);
			}
			else
			{
				init_dir_entry(view, dir_entry, name);
				dir_entry->origin = strdup("/");
			}
			free(typed_path);
			dir_entry->owns_origin = 1;
		}
		else
		{
			char parent_path[PATH_MAX + 1];
			const intptr_t *parent_idx = parent_data;
			init_dir_entry(view, dir_entry, name);
			get_full_path_of(&(*entries)[*parent_idx], sizeof(parent_path),
					parent_path);
			dir_entry->origin = strdup(parent_path);
			dir_entry->owns_origin = 1;
		}

		get_full_path_of(dir_entry, sizeof(full_path), full_path);
		fill_dir_entry_by_path(dir_entry, full_path);

		dir_entry->temporary = in_place;
	}

	*(intptr_t *)data = dir_entry - *entries;

	if(parent_data != NULL)
	{
		const intptr_t *const parent_idx = parent_data;
		dir_entry->child_pos = (dir_entry - *entries) - *parent_idx;

		do
		{
			dir_entry -= dir_entry->child_pos;
			++dir_entry->child_count;
		}
		while(dir_entry->child_pos != 0);
	}
	return 0;
}

/* Replaces list of entries with a single parent directory entry.  Might leave
 * the list empty on memory error. */
static void
reset_entry_list(view_t *view, dir_entry_t **entries, int *count)
{
	free_dir_entries(entries, count);
	add_parent_entry(view, entries, count);
}

/* Traverses root children and drops fake root nodes and optionally extra tops
 * of subtrees. */
static void
drop_tops(dir_entry_t *entries, int *nentries, int extra)
{
	int i;
	for(i = 0; i < *nentries - 1; i += entries[i].child_count + 1)
	{
		int j = i;
		while(j < *nentries - 1 && entries[j].child_count > 0 &&
				((extra && entries[j].child_count == entries[j + 1].child_count + 1) ||
				 entries[j].name[0] == '\0') && entries[j].tag != 1)
		{
			fentry_free(&entries[j++]);
		}

		memmove(&entries[i], &entries[j], sizeof(*entries)*(*nentries - j));
		*nentries -= j - i;
		entries[i].child_pos = 0;
	}
}

/* Adds custom view entries corresponding to file system tree.  parent_pos is
 * expected to be negative for the outermost invocation.  The depth parameter
 * is used to limit nesting level, when it's negative, parent node is just
 * marked as folded.  Returns number of filtered out files on success or partial
 * success and negative value on serious error. */
static int
add_files_recursively(view_t *view, const char path[], trie_t *excluded_paths,
		trie_t *folded_paths, int parent_pos, int no_direct_parent, int depth)
{
	int i;
	const int prev_count = view->custom.entry_count;
	int nfiltered = 0;

	int len;
	char **lst = list_all_files(path, &len);
	if(len < 0)
	{
		return -1;
	}

	FoldState parent_fold = get_fold_state(folded_paths, path);

	for(i = 0; i < len && !ui_cancellation_requested(); ++i)
	{
		int dir;
		void *dummy;
		dir_entry_t *entry;
		char *const full_path = format_str("%s/%s", path, lst[i]);

		if(trie_get(excluded_paths, full_path, &dummy) == 0)
		{
			free(full_path);
			continue;
		}

		dir = is_dir(full_path);
		if(!tree_candidate_is_visible(view, path, lst[i], dir, 1))
		{
			const int real_dir = (dir && !is_symlink(full_path));

			FoldState state;
			if(real_dir)
			{
				state = get_fold_state(folded_paths, full_path);
			}

			if(real_dir &&
					(depth == 0 ||
					 (state == FOLD_UNDEFINED && parent_fold == FOLD_AUTO_OPENED)))
			{
				if(set_fold_state(folded_paths, full_path, FOLD_AUTO_CLOSED))
				{
					state = FOLD_AUTO_CLOSED;
				}
			}

			/* Traverse directory (but not symlink to it) even if we're skipping it,
			 * because we might need files that are inside of it. */
			if(real_dir && depth > 0 &&
					tree_candidate_is_visible(view, path, lst[i], dir, 0))
			{
				if(state != FOLD_AUTO_CLOSED && state != FOLD_USER_CLOSED)
				{
					nfiltered += add_files_recursively(view, full_path, excluded_paths,
							folded_paths, parent_pos, 1, depth - 1);
				}
			}

			free(full_path);
			++nfiltered;
			continue;
		}

		entry = flist_custom_add(view, full_path);
		if(entry == NULL)
		{
			free(full_path);
			free_string_array(lst, len);
			return -1;
		}

		if(parent_pos >= 0)
		{
			entry->child_pos = (view->custom.entry_count - 1) - parent_pos;
		}

		/* Not using dir variable here, because it is set for symlinks to
		 * directories as well. */
		FoldState state = get_fold_state(folded_paths, full_path);
		entry->folded = (entry->type == FT_DIR)
		             && (state == FOLD_USER_CLOSED || state == FOLD_AUTO_CLOSED);

		if(entry->type == FT_DIR && !entry->folded)
		{
			if(depth == 0 ||
					(state == FOLD_UNDEFINED && parent_fold == FOLD_AUTO_OPENED))
			{
				if(set_fold_state(folded_paths, full_path, FOLD_AUTO_CLOSED))
				{
					entry->folded = 1;
				}
			}
			else
			{
				const int idx = view->custom.entry_count - 1;
				const int filtered = add_files_recursively(view, full_path,
						excluded_paths, folded_paths, idx, 0, depth - 1);
				/* Keep going in case of error and load partial list. */
				if(filtered >= 0)
				{
					/* If one of recursive calls returned error, keep going and build
					* partial tree. */
					view->custom.entries[idx].child_count = (view->custom.entry_count - 1)
					                                      - idx;
					nfiltered += filtered;
				}
			}
		}

		free(full_path);

		show_progress("Building tree...", 1000);
	}

	free_string_array(lst, len);

	/* The prev_count != 0 check is to make sure that we won't create leaf instead
	 * of the whole tree (this is handled in flist_custom_finish()). */
	int show_empty_dir_leafs = (cfg.dot_dirs & DD_TREE_LEAFS_PARENT);
	if(show_empty_dir_leafs && !no_direct_parent && prev_count != 0 &&
			view->custom.entry_count == prev_count)
	{
		/* To be able to perform operations inside directory (e.g., create files),
		 * we need at least one element there. */
		if(add_directory_leaf(view, path, parent_pos) != 0)
		{
			return -1;
		}
	}

	return nfiltered;
}

/* Retrieves state of the fold if present.  Returns the state or
 * FOLD_UNDEFINED. */
static FoldState
get_fold_state(trie_t *folded_paths, const char full_path[])
{
	void *data;
	if(trie_get(folded_paths, full_path, &data) != 0)
	{
		return FOLD_UNDEFINED;
	}

	return (FoldState)(uintptr_t)data;
}

/* Sets state of a fold. */
static int
set_fold_state(trie_t *folded_paths, const char full_path[], FoldState state)
{
	return (trie_set(folded_paths, full_path, (void *)state) >= 0);
}

/* Checks whether file is visible according to all filters.  Returns non-zero if
 * so, otherwise zero is returned. */
static int
entry_is_visible(view_t *view, const char name[], const void *data)
{
	if(view->hide_dot && name[0] == '.')
	{
		return 0;
	}

	char full_path[PATH_MAX + 1 + NAME_MAX + 1];
	snprintf(full_path, sizeof(full_path), "%s/%s", flist_get_dir(view), name);

	const int is_dir = data_is_dir_entry(data, full_path);
	return filters_file_is_visible(view, flist_get_dir(view), name, is_dir,
			/*apply_local_filter=*/1);
}

/* Checks whether a candidate for adding to a tree is visible according to
 * filters.  Returns non-zero if so, otherwise zero is returned. */
static int
tree_candidate_is_visible(view_t *view, const char path[], const char name[],
		int is_dir, int apply_local_filter)
{
	if(view->hide_dot && name[0] == '.')
	{
		return 0;
	}

	return filters_file_is_visible(view, path, name, is_dir, apply_local_filter);
}

/* Adds ".." directory leaf of an empty directory to the tree which is being
 * built.  Returns non-zero on error, otherwise zero is returned. */
static int
add_directory_leaf(view_t *view, const char path[], int parent_pos)
{
	char *full_path;
	dir_entry_t *entry;

	/* If local filter isn't empty, assume that user is looking for something and
	 * leafs will get in his way. */
	if(!filter_is_empty(&view->local_filter.filter))
	{
		return 0;
	}

	entry = alloc_dir_entry(&view->custom.entries, view->custom.entry_count);
	if(entry == NULL)
	{
		show_error_msg("Memory Error", "Unable to allocate enough memory");
		return 1;
	}

	full_path = format_str("%s/..", path);
	if(init_parent_entry(view, entry, full_path) != 0)
	{
		free(full_path);
		/* Keep going in case of error and load partial list. */
		return 0;
	}

	remove_last_path_component(full_path);
	entry->origin = full_path;
	entry->owns_origin = 1;

	if(parent_pos >= 0)
	{
		entry->child_pos = (view->custom.entry_count - 1) - parent_pos;
	}

	++view->custom.entry_count;
	return 0;
}

/* Fills given entry of the view at specified path (can be relative, i.e. "..").
 * Returns zero on success, otherwise non-zero is returned. */
static int
init_parent_entry(view_t *view, dir_entry_t *entry, const char path[])
{
	struct stat s;

	init_dir_entry(view, entry, get_last_path_component(path));
	entry->type = FT_DIR;

	/* Load the inode info or leave blank values in entry. */
	if(os_lstat(path, &s) != 0)
	{
		fentry_free(entry);
		LOG_SERROR_MSG(errno, "Can't lstat() \"%s\"", path);
		log_cwd();
		return 1;
	}

#ifndef _WIN32
	entry->size = (uintmax_t)s.st_size;
	entry->uid = s.st_uid;
	entry->gid = s.st_gid;
	entry->mode = s.st_mode;
	entry->inode = s.st_ino;
#else
	/* Windows doesn't like returning size of directories even if it can. */
	entry->size = get_file_size(entry->name);
#endif
	entry->mtime = s.st_mtime;
	entry->atime = s.st_atime;
	entry->ctime = s.st_ctime;
	entry->nlinks = s.st_nlink;

	return 0;
}

int
flist_is_fs_backed(const view_t *view)
{
	/* Custom trees don't track file-system changes. */
	return (!flist_custom_active(view) || view->custom.type == CV_TREE);
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
