summaryrefslogtreecommitdiffstats
path: root/src/filemanager/treestore.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/filemanager/treestore.c')
-rw-r--r--src/filemanager/treestore.c941
1 files changed, 941 insertions, 0 deletions
diff --git a/src/filemanager/treestore.c b/src/filemanager/treestore.c
new file mode 100644
index 0000000..2d23c93
--- /dev/null
+++ b/src/filemanager/treestore.c
@@ -0,0 +1,941 @@
+/*
+ Tree Store
+ Contains a storage of the file system tree representation
+
+ This module has been converted to be a widget.
+
+ The program load and saves the tree each time the tree widget is
+ created and destroyed. This is required for the future vfs layer,
+ it will be possible to have tree views over virtual file systems.
+
+ Copyright (C) 1999-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1996
+ Norbert Warmuth, 1997
+ Miguel de Icaza, 1996, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file treestore.c
+ * \brief Source: tree store
+ *
+ * Contains a storage of the file system tree representation.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+#include "lib/vfs/vfs.h"
+#include "lib/fileloc.h"
+#include "lib/strescape.h"
+#include "lib/hook.h"
+#include "lib/util.h"
+
+#include "src/setup.h" /* setup_init() */
+
+#include "treestore.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static tree_entry *tree_store_add_entry (const vfs_path_t * name);
+
+/*** file scope variables ************************************************************************/
+
+static struct TreeStore ts;
+
+static hook_t *remove_entry_hooks;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_store_dirty (gboolean dirty)
+{
+ ts.dirty = dirty;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ *
+ * @return the number of common bytes in the strings.
+ */
+
+static size_t
+str_common (const vfs_path_t * s1_vpath, const vfs_path_t * s2_vpath)
+{
+ size_t result = 0;
+ const char *s1, *s2;
+
+ s1 = vfs_path_as_str (s1_vpath);
+ s2 = vfs_path_as_str (s2_vpath);
+
+ while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
+ result++;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** The directory names are arranged in a single linked list in the same
+ * order as they are displayed. When the tree is displayed the expected
+ * order is like this:
+ * /
+ * /bin
+ * /etc
+ * /etc/X11
+ * /etc/rc.d
+ * /etc.old/X11
+ * /etc.old/rc.d
+ * /usr
+ *
+ * i.e. the required collating sequence when comparing two directory names is
+ * '\0' < PATH_SEP < all-other-characters-in-encoding-order
+ *
+ * Since strcmp doesn't fulfil this requirement we use pathcmp when
+ * inserting directory names into the list. The meaning of the return value
+ * of pathcmp and strcmp are the same (an integer less than, equal to, or
+ * greater than zero if p1 is found to be less than, to match, or be greater
+ * than p2.
+ */
+
+static int
+pathcmp (const vfs_path_t * p1_vpath, const vfs_path_t * p2_vpath)
+{
+ int ret_val;
+ const char *p1, *p2;
+
+ p1 = vfs_path_as_str (p1_vpath);
+ p2 = vfs_path_as_str (p2_vpath);
+
+ for (; *p1 == *p2; p1++, p2++)
+ if (*p1 == '\0')
+ return 0;
+
+ if (*p1 == '\0')
+ ret_val = -1;
+ else if (*p2 == '\0')
+ ret_val = 1;
+ else if (IS_PATH_SEP (*p1))
+ ret_val = -1;
+ else if (IS_PATH_SEP (*p2))
+ ret_val = 1;
+ else
+ ret_val = (*p1 - *p2);
+
+ return ret_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+decode (char *buffer)
+{
+ char *res, *p, *q;
+
+ res = g_strdup (buffer);
+
+ for (p = q = res; *p != '\0'; p++, q++)
+ {
+ if (*p == '\n')
+ {
+ *q = '\0';
+ return res;
+ }
+
+ if (*p != '\\')
+ {
+ *q = *p;
+ continue;
+ }
+
+ p++;
+
+ switch (*p)
+ {
+ case 'n':
+ *q = '\n';
+ break;
+ case '\\':
+ *q = '\\';
+ break;
+ default:
+ break;
+ }
+ }
+
+ *q = *p;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Loads the tree store from the specified filename */
+
+static int
+tree_store_load_from (const char *name)
+{
+ FILE *file;
+ char buffer[MC_MAXPATHLEN + 20];
+
+ g_return_val_if_fail (name != NULL, 0);
+
+ if (ts.loaded)
+ return 1;
+
+ file = fopen (name, "r");
+
+ if (file != NULL
+ && (fgets (buffer, sizeof (buffer), file) == NULL
+ || strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0))
+ {
+ fclose (file);
+ file = NULL;
+ }
+
+ if (file != NULL)
+ {
+ char oldname[MC_MAXPATHLEN] = "\0";
+
+ ts.loaded = TRUE;
+
+ /* File open -> read contents */
+ while (fgets (buffer, MC_MAXPATHLEN, file))
+ {
+ tree_entry *e;
+ gboolean scanned;
+ char *lc_name;
+
+ /* Skip invalid records */
+ if (buffer[0] != '0' && buffer[0] != '1')
+ continue;
+
+ if (buffer[1] != ':')
+ continue;
+
+ scanned = buffer[0] == '1';
+
+ lc_name = decode (buffer + 2);
+ if (!IS_PATH_SEP (lc_name[0]))
+ {
+ /* Clear-text decompression */
+ char *s;
+
+ s = strtok (lc_name, " ");
+ if (s != NULL)
+ {
+ char *different;
+ int common;
+
+ common = atoi (s);
+ different = strtok (NULL, "");
+ if (different != NULL)
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (oldname);
+ g_strlcpy (oldname + common, different, sizeof (oldname) - (size_t) common);
+ if (vfs_file_is_local (vpath))
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_from_str (oldname);
+ e = tree_store_add_entry (tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ e->scanned = scanned;
+ }
+ vfs_path_free (vpath, TRUE);
+ }
+ }
+ }
+ else
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (lc_name);
+ if (vfs_file_is_local (vpath))
+ {
+ e = tree_store_add_entry (vpath);
+ e->scanned = scanned;
+ }
+ vfs_path_free (vpath, TRUE);
+ g_strlcpy (oldname, lc_name, sizeof (oldname));
+ }
+ g_free (lc_name);
+ }
+
+ fclose (file);
+ }
+
+ /* Nothing loaded, we add some standard directories */
+ if (!ts.tree_first)
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_from_str (PATH_SEP_STR);
+ tree_store_add_entry (tmp_vpath);
+ tree_store_rescan (tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ ts.loaded = TRUE;
+ }
+
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+encode (const vfs_path_t * vpath, size_t offset)
+{
+ return strutils_escape (vfs_path_as_str (vpath) + offset, -1, "\n\\", FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Saves the tree to the specified filename */
+
+static int
+tree_store_save_to (char *name)
+{
+ tree_entry *current;
+ FILE *file;
+
+ file = fopen (name, "w");
+ if (file == NULL)
+ return errno;
+
+ fprintf (file, "%s\n", TREE_SIGNATURE);
+
+ for (current = ts.tree_first; current != NULL; current = current->next)
+ if (vfs_file_is_local (current->name))
+ {
+ int i, common;
+
+ /* Clear-text compression */
+ if (current->prev != NULL
+ && (common = str_common (current->prev->name, current->name)) > 2)
+ {
+ char *encoded;
+
+ encoded = encode (current->name, common);
+ i = fprintf (file, "%d:%d %s\n", current->scanned ? 1 : 0, common, encoded);
+ g_free (encoded);
+ }
+ else
+ {
+ char *encoded;
+
+ encoded = encode (current->name, 0);
+ i = fprintf (file, "%d:%s\n", current->scanned ? 1 : 0, encoded);
+ g_free (encoded);
+ }
+
+ if (i == EOF)
+ {
+ fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
+ name, unix_error_string (errno));
+ break;
+ }
+ }
+
+ tree_store_dirty (FALSE);
+ fclose (file);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+tree_store_add_entry (const vfs_path_t * name)
+{
+ int flag = -1;
+ tree_entry *current;
+ tree_entry *old = NULL;
+ tree_entry *new;
+ int submask = 0;
+
+ if (ts.tree_last != NULL && ts.tree_last->next != NULL)
+ abort ();
+
+ /* Search for the correct place */
+ for (current = ts.tree_first;
+ current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
+ old = current;
+
+ if (flag == 0)
+ return current; /* Already in the list */
+
+ /* Not in the list -> add it */
+ new = g_new0 (tree_entry, 1);
+ if (current == NULL)
+ {
+ /* Append to the end of the list */
+ if (ts.tree_first == NULL)
+ {
+ /* Empty list */
+ ts.tree_first = new;
+ new->prev = NULL;
+ }
+ else
+ {
+ if (old != NULL)
+ old->next = new;
+ new->prev = old;
+ }
+ new->next = NULL;
+ ts.tree_last = new;
+ }
+ else
+ {
+ /* Insert in to the middle of the list */
+ new->prev = old;
+ if (old != NULL)
+ {
+ /* Yes, in the middle */
+ new->next = old->next;
+ old->next = new;
+ }
+ else
+ {
+ /* Nope, in the beginning of the list */
+ new->next = ts.tree_first;
+ ts.tree_first = new;
+ }
+ new->next->prev = new;
+ }
+
+ /* Calculate attributes */
+ new->name = vfs_path_clone (name);
+ new->sublevel = vfs_path_tokens_count (new->name);
+
+ {
+ const char *new_name;
+
+ new_name = vfs_path_get_last_path_str (new->name);
+ new->subname = strrchr (new_name, PATH_SEP);
+ if (new->subname == NULL)
+ new->subname = new_name;
+ else
+ new->subname++;
+ }
+
+ if (new->next != NULL)
+ submask = new->next->submask;
+
+ submask |= 1 << new->sublevel;
+ submask &= (2 << new->sublevel) - 1;
+ new->submask = submask;
+ new->mark = FALSE;
+
+ /* Correct the submasks of the previous entries */
+ for (current = new->prev;
+ current != NULL && current->sublevel > new->sublevel; current = current->prev)
+ current->submask |= 1 << new->sublevel;
+
+ tree_store_dirty (TRUE);
+ return new;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_store_notify_remove (tree_entry * entry)
+{
+ hook_t *p;
+
+ for (p = remove_entry_hooks; p != NULL; p = p->next)
+ {
+ tree_store_remove_fn r = (tree_store_remove_fn) p->hook_fn;
+
+ r (entry, p->hook_data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+remove_entry (tree_entry * entry)
+{
+ tree_entry *current = entry->prev;
+ long submask = 0;
+ tree_entry *ret = NULL;
+
+ tree_store_notify_remove (entry);
+
+ /* Correct the submasks of the previous entries */
+ if (entry->next != NULL)
+ submask = entry->next->submask;
+
+ for (; current != NULL && current->sublevel > entry->sublevel; current = current->prev)
+ {
+ submask |= 1 << current->sublevel;
+ submask &= (2 << current->sublevel) - 1;
+ current->submask = submask;
+ }
+
+ /* Unlink the entry from the list */
+ if (entry->prev != NULL)
+ entry->prev->next = entry->next;
+ else
+ ts.tree_first = entry->next;
+
+ if (entry->next != NULL)
+ entry->next->prev = entry->prev;
+ else
+ ts.tree_last = entry->prev;
+
+ /* Free the memory used by the entry */
+ vfs_path_free (entry->name, TRUE);
+ g_free (entry);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+process_special_dirs (GList ** special_dirs, const char *file)
+{
+ gchar **start_buff;
+ mc_config_t *cfg;
+
+ cfg = mc_config_init (file, TRUE);
+ if (cfg == NULL)
+ return;
+
+ start_buff = mc_config_get_string_list (cfg, "Special dirs", "list", NULL);
+ if (start_buff != NULL)
+ {
+ gchar **buffers;
+
+ for (buffers = start_buff; *buffers != NULL; buffers++)
+ {
+ *special_dirs = g_list_prepend (*special_dirs, *buffers);
+ *buffers = NULL;
+ }
+
+ g_strfreev (start_buff);
+ }
+ mc_config_deinit (cfg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+should_skip_directory (const vfs_path_t * vpath)
+{
+ static GList *special_dirs = NULL;
+ GList *l;
+ static gboolean loaded = FALSE;
+ gboolean ret = FALSE;
+
+ if (!loaded)
+ {
+ const char *profile_name;
+
+ profile_name = setup_init ();
+ process_special_dirs (&special_dirs, profile_name);
+ process_special_dirs (&special_dirs, mc_global.profile_name);
+
+ loaded = TRUE;
+ }
+
+ for (l = special_dirs; l != NULL; l = g_list_next (l))
+ if (strncmp (vfs_path_as_str (vpath), l->data, strlen (l->data)) == 0)
+ {
+ ret = TRUE;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+queue_vpath_free (gpointer data)
+{
+ vfs_path_free ((vfs_path_t *) data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Searches for specified directory */
+tree_entry *
+tree_store_whereis (const vfs_path_t * name)
+{
+ tree_entry *current;
+ int flag = -1;
+
+ for (current = ts.tree_first;
+ current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
+ ;
+
+ return flag == 0 ? current : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct TreeStore *
+tree_store_get (void)
+{
+ return &ts;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * \fn int tree_store_load(void)
+ * \brief Loads the tree from the default location
+ * \return 1 if success (true), 0 otherwise (false)
+ */
+
+int
+tree_store_load (void)
+{
+ char *name;
+ int retval;
+
+ name = mc_config_get_full_path (MC_TREESTORE_FILE);
+ retval = tree_store_load_from (name);
+ g_free (name);
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * \fn int tree_store_save(void)
+ * \brief Saves the tree to the default file in an atomic fashion
+ * \return 0 if success, errno on error
+ */
+
+int
+tree_store_save (void)
+{
+ char *name;
+ int retval;
+
+ name = mc_config_get_full_path (MC_TREESTORE_FILE);
+ mc_util_make_backup_if_possible (name, ".tmp");
+
+ retval = tree_store_save_to (name);
+ if (retval != 0)
+ mc_util_restore_from_backup_if_possible (name, ".tmp");
+ else
+ mc_util_unlink_backup_if_possible (name, ".tmp");
+
+ g_free (name);
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
+{
+ add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
+{
+ delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_store_remove_entry (const vfs_path_t * name_vpath)
+{
+ tree_entry *current, *base;
+ size_t len;
+
+ g_return_if_fail (name_vpath != NULL);
+
+ /* Miguel Ugly hack */
+ {
+ gboolean is_root;
+ const char *name_vpath_str;
+
+ name_vpath_str = vfs_path_as_str (name_vpath);
+ is_root = (IS_PATH_SEP (name_vpath_str[0]) && name_vpath_str[1] == '\0');
+ if (is_root)
+ return;
+ }
+ /* Miguel Ugly hack end */
+
+ base = tree_store_whereis (name_vpath);
+ if (base == NULL)
+ return; /* Doesn't exist */
+
+ len = vfs_path_len (base->name);
+ current = base->next;
+ while (current != NULL && vfs_path_equal_len (current->name, base->name, len))
+ {
+ gboolean ok;
+ tree_entry *old;
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]));
+ if (!ok)
+ break;
+
+ old = current;
+ current = current->next;
+ remove_entry (old);
+ }
+ remove_entry (base);
+ tree_store_dirty (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This subdirectory exists -> clear deletion mark */
+
+void
+tree_store_mark_checked (const char *subname)
+{
+ vfs_path_t *name;
+ tree_entry *current, *base;
+ int flag = 1;
+ const char *cname;
+
+ if (!ts.loaded)
+ return;
+
+ if (ts.check_name == NULL)
+ return;
+
+ /* Calculate the full name of the subdirectory */
+ if (DIR_IS_DOT (subname) || DIR_IS_DOTDOT (subname))
+ return;
+
+ cname = vfs_path_as_str (ts.check_name);
+ if (IS_PATH_SEP (cname[0]) && cname[1] == '\0')
+ name = vfs_path_build_filename (PATH_SEP_STR, subname, (char *) NULL);
+ else
+ name = vfs_path_append_new (ts.check_name, subname, (char *) NULL);
+
+ /* Search for the subdirectory */
+ for (current = ts.check_start;
+ current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
+ ;
+
+ if (flag != 0)
+ {
+ /* Doesn't exist -> add it */
+ current = tree_store_add_entry (name);
+ ts.add_queue_vpath = g_list_prepend (ts.add_queue_vpath, name);
+ }
+ else
+ vfs_path_free (name, TRUE);
+
+ /* Clear the deletion mark from the subdirectory and its children */
+ base = current;
+ if (base != NULL)
+ {
+ size_t len;
+
+ len = vfs_path_len (base->name);
+ base->mark = FALSE;
+ for (current = base->next;
+ current != NULL && vfs_path_equal_len (current->name, base->name, len);
+ current = current->next)
+ {
+ gboolean ok;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
+ if (!ok)
+ break;
+
+ current->mark = FALSE;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Mark the subdirectories of the current directory for delete */
+
+tree_entry *
+tree_store_start_check (const vfs_path_t * vpath)
+{
+ tree_entry *current, *retval;
+ size_t len;
+
+ if (!ts.loaded)
+ return NULL;
+
+ g_return_val_if_fail (ts.check_name == NULL, NULL);
+ ts.check_start = NULL;
+
+ /* Search for the start of subdirectories */
+ current = tree_store_whereis (vpath);
+ if (current == NULL)
+ {
+ struct stat s;
+
+ if (mc_stat (vpath, &s) == -1 || !S_ISDIR (s.st_mode))
+ return NULL;
+
+ current = tree_store_add_entry (vpath);
+ ts.check_name = vfs_path_clone (vpath);
+
+ return current;
+ }
+
+ ts.check_name = vfs_path_clone (vpath);
+
+ retval = current;
+
+ /* Mark old subdirectories for delete */
+ ts.check_start = current->next;
+ len = vfs_path_len (ts.check_name);
+
+ for (current = ts.check_start;
+ current != NULL && vfs_path_equal_len (current->name, ts.check_name, len);
+ current = current->next)
+ {
+ gboolean ok;
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
+ if (!ok)
+ break;
+
+ current->mark = TRUE;
+ }
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Delete subdirectories which still have the deletion mark */
+
+void
+tree_store_end_check (void)
+{
+ tree_entry *current;
+ size_t len;
+ GList *the_queue;
+
+ if (!ts.loaded)
+ return;
+
+ g_return_if_fail (ts.check_name != NULL);
+
+ /* Check delete marks and delete if found */
+ len = vfs_path_len (ts.check_name);
+
+ current = ts.check_start;
+ while (current != NULL && vfs_path_equal_len (current->name, ts.check_name, len))
+ {
+ gboolean ok;
+ tree_entry *old;
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
+ if (!ok)
+ break;
+
+ old = current;
+ current = current->next;
+ if (old->mark)
+ remove_entry (old);
+ }
+
+ /* get the stuff in the scan order */
+ the_queue = g_list_reverse (ts.add_queue_vpath);
+ ts.add_queue_vpath = NULL;
+ vfs_path_free (ts.check_name, TRUE);
+ ts.check_name = NULL;
+
+ g_list_free_full (the_queue, queue_vpath_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+tree_entry *
+tree_store_rescan (const vfs_path_t * vpath)
+{
+ DIR *dirp;
+ struct stat buf;
+ tree_entry *entry;
+
+ if (should_skip_directory (vpath))
+ {
+ entry = tree_store_add_entry (vpath);
+ entry->scanned = TRUE;
+ return entry;
+ }
+
+ entry = tree_store_start_check (vpath);
+ if (entry == NULL)
+ return NULL;
+
+ dirp = mc_opendir (vpath);
+ if (dirp != NULL)
+ {
+ struct vfs_dirent *dp;
+
+ for (dp = mc_readdir (dirp); dp != NULL; dp = mc_readdir (dirp))
+ if (!DIR_IS_DOT (dp->d_name) && !DIR_IS_DOTDOT (dp->d_name))
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_append_new (vpath, dp->d_name, (char *) NULL);
+ if (mc_lstat (tmp_vpath, &buf) != -1 && S_ISDIR (buf.st_mode))
+ tree_store_mark_checked (dp->d_name);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ mc_closedir (dirp);
+ }
+ tree_store_end_check ();
+ entry->scanned = TRUE;
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */