mirror of
https://github.com/MidnightCommander/mc
synced 2025-01-05 11:04:42 +03:00
6718b3ec26
Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
942 lines
24 KiB
C
942 lines
24 KiB
C
/*
|
|
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-2024
|
|
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/strutil.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 str_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;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|