mirror of
https://github.com/MidnightCommander/mc
synced 2024-12-23 04:46:55 +03:00
e4e135399f
This reverts commit 54d6ec88dc
.
Conflicts:
ChangeLog
mhl/string.h
src/command.c
src/complete.c
src/util.c
811 lines
17 KiB
C
811 lines
17 KiB
C
/*
|
|
* Tree Store
|
|
*
|
|
* Contains a storage of the file system tree representation
|
|
*
|
|
Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007
|
|
Free Software Foundation, Inc.
|
|
|
|
Written: 1994, 1996 Janne Kukonlehto
|
|
1997 Norbert Warmuth
|
|
1996, 1999 Miguel de Icaza
|
|
|
|
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.
|
|
|
|
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.
|
|
*/
|
|
|
|
#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 "global.h"
|
|
#include "treestore.h"
|
|
#include "profile.h"
|
|
#include "setup.h"
|
|
|
|
#define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
|
|
|
|
static struct TreeStore ts;
|
|
|
|
static tree_entry *tree_store_add_entry(const char *name);
|
|
|
|
static void
|
|
tree_store_dirty(int state)
|
|
{
|
|
ts.dirty = state;
|
|
}
|
|
|
|
/* Returns the number of common bytes in the strings. */
|
|
static size_t
|
|
str_common(const char *s1, const char *s2)
|
|
{
|
|
size_t result = 0;
|
|
|
|
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 char *p1, const char *p2)
|
|
{
|
|
for (; *p1 == *p2; p1++, p2++)
|
|
if (*p1 == '\0')
|
|
return 0;
|
|
|
|
if (*p1 == '\0')
|
|
return -1;
|
|
if (*p2 == '\0')
|
|
return 1;
|
|
if (*p1 == PATH_SEP)
|
|
return -1;
|
|
if (*p2 == PATH_SEP)
|
|
return 1;
|
|
return (*p1 - *p2);
|
|
}
|
|
|
|
/* Searches for specified directory */
|
|
tree_entry *
|
|
tree_store_whereis(const char *name)
|
|
{
|
|
tree_entry *current = ts.tree_first;
|
|
int flag = -1;
|
|
|
|
while (current && (flag = pathcmp(current->name, name)) < 0)
|
|
current = current->next;
|
|
|
|
if (flag == 0)
|
|
return current;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
struct TreeStore *
|
|
tree_store_get(void)
|
|
{
|
|
return &ts;
|
|
}
|
|
|
|
static char *
|
|
decode(char *buffer)
|
|
{
|
|
char *res = g_strdup(buffer);
|
|
char *p, *q;
|
|
|
|
for (p = q = res; *p; 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;
|
|
}
|
|
}
|
|
*q = *p;
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Loads the tree store from the specified filename */
|
|
static int
|
|
tree_store_load_from(char *name)
|
|
{
|
|
FILE *file;
|
|
char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
|
|
char *different;
|
|
int len, common;
|
|
int do_load;
|
|
|
|
g_return_val_if_fail(name != NULL, FALSE);
|
|
|
|
if (ts.loaded)
|
|
return TRUE;
|
|
|
|
file = fopen(name, "r");
|
|
|
|
if (file) {
|
|
fgets(buffer, sizeof(buffer), file);
|
|
|
|
if (strncmp(buffer, TREE_SIGNATURE, strlen(TREE_SIGNATURE)) != 0) {
|
|
fclose(file);
|
|
do_load = FALSE;
|
|
} else
|
|
do_load = TRUE;
|
|
} else
|
|
do_load = FALSE;
|
|
|
|
if (do_load) {
|
|
ts.loaded = TRUE;
|
|
|
|
/* File open -> read contents */
|
|
oldname[0] = 0;
|
|
while (fgets(buffer, MC_MAXPATHLEN, file)) {
|
|
tree_entry *e;
|
|
int scanned;
|
|
char *name;
|
|
|
|
/* Skip invalid records */
|
|
if ((buffer[0] != '0' && buffer[0] != '1'))
|
|
continue;
|
|
|
|
if (buffer[1] != ':')
|
|
continue;
|
|
|
|
scanned = buffer[0] == '1';
|
|
|
|
name = decode(buffer + 2);
|
|
|
|
len = strlen(name);
|
|
if (name[0] != PATH_SEP) {
|
|
/* Clear-text decompression */
|
|
char *s = strtok(name, " ");
|
|
|
|
if (s) {
|
|
common = atoi(s);
|
|
different = strtok(NULL, "");
|
|
if (different) {
|
|
strcpy(oldname + common, different);
|
|
if (vfs_file_is_local(oldname)) {
|
|
e = tree_store_add_entry(oldname);
|
|
e->scanned = scanned;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (vfs_file_is_local(name)) {
|
|
e = tree_store_add_entry(name);
|
|
e->scanned = scanned;
|
|
}
|
|
strcpy(oldname, name);
|
|
}
|
|
g_free(name);
|
|
}
|
|
fclose(file);
|
|
}
|
|
|
|
/* Nothing loaded, we add some standard directories */
|
|
if (!ts.tree_first) {
|
|
tree_store_add_entry(PATH_SEP_STR);
|
|
tree_store_rescan(PATH_SEP_STR);
|
|
ts.loaded = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* tree_store_load:
|
|
* @void:
|
|
*
|
|
* Loads the tree from the default location.
|
|
*
|
|
* Return value: TRUE if success, FALSE otherwise.
|
|
**/
|
|
int
|
|
tree_store_load(void)
|
|
{
|
|
char *name;
|
|
int retval;
|
|
|
|
name = concat_dir_and_file(home_dir, MC_TREE);
|
|
retval = tree_store_load_from(name);
|
|
g_free(name);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static char *
|
|
encode(const char *string)
|
|
{
|
|
int special_chars;
|
|
const char *p;
|
|
char *q;
|
|
char *res;
|
|
|
|
for (special_chars = 0, p = string; *p; p++) {
|
|
if (*p == '\n' || *p == '\\')
|
|
special_chars++;
|
|
}
|
|
|
|
res = g_malloc(p - string + special_chars + 1);
|
|
for (p = string, q = res; *p; p++, q++) {
|
|
if (*p != '\n' && *p != '\\') {
|
|
*q = *p;
|
|
continue;
|
|
}
|
|
|
|
*q++ = '\\';
|
|
|
|
switch (*p) {
|
|
case '\n':
|
|
*q = 'n';
|
|
break;
|
|
|
|
case '\\':
|
|
*q = '\\';
|
|
break;
|
|
}
|
|
}
|
|
*q = 0;
|
|
return res;
|
|
}
|
|
|
|
/* 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)
|
|
return errno;
|
|
|
|
fprintf(file, "%s\n", TREE_SIGNATURE);
|
|
|
|
current = ts.tree_first;
|
|
while (current) {
|
|
int i, common;
|
|
|
|
if (vfs_file_is_local(current->name)) {
|
|
/* Clear-text compression */
|
|
if (current->prev
|
|
&& (common =
|
|
str_common(current->prev->name, current->name)) > 2) {
|
|
char *encoded = encode(current->name + common);
|
|
|
|
i = fprintf(file, "%d:%d %s\n", current->scanned, common,
|
|
encoded);
|
|
g_free(encoded);
|
|
} else {
|
|
char *encoded = encode(current->name);
|
|
|
|
i = fprintf(file, "%d:%s\n", current->scanned, encoded);
|
|
g_free(encoded);
|
|
}
|
|
|
|
if (i == EOF) {
|
|
fprintf(stderr, _("Cannot write to the %s file:\n%s\n"),
|
|
name, unix_error_string(errno));
|
|
break;
|
|
}
|
|
}
|
|
current = current->next;
|
|
}
|
|
tree_store_dirty(FALSE);
|
|
fclose(file);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tree_store_save:
|
|
* @void:
|
|
*
|
|
* Saves the tree to the default file in an atomic fashion.
|
|
*
|
|
* Return value: 0 if success, errno on error.
|
|
**/
|
|
int
|
|
tree_store_save(void)
|
|
{
|
|
char *tmp;
|
|
char *name;
|
|
int retval;
|
|
|
|
tmp = concat_dir_and_file(home_dir, MC_TREE_TMP);
|
|
retval = tree_store_save_to(tmp);
|
|
|
|
if (retval) {
|
|
g_free(tmp);
|
|
return retval;
|
|
}
|
|
|
|
name = concat_dir_and_file(home_dir, MC_TREE);
|
|
retval = rename(tmp, name);
|
|
|
|
g_free(tmp);
|
|
g_free(name);
|
|
|
|
if (retval)
|
|
return errno;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static tree_entry *
|
|
tree_store_add_entry(const char *name)
|
|
{
|
|
int flag = -1;
|
|
tree_entry *current = ts.tree_first;
|
|
tree_entry *old = NULL;
|
|
tree_entry *new;
|
|
int i, len;
|
|
int submask = 0;
|
|
|
|
if (ts.tree_last && ts.tree_last->next)
|
|
abort();
|
|
|
|
/* Search for the correct place */
|
|
while (current && (flag = pathcmp(current->name, name)) < 0) {
|
|
old = current;
|
|
current = current->next;
|
|
}
|
|
|
|
if (flag == 0)
|
|
return current; /* Already in the list */
|
|
|
|
/* Not in the list -> add it */
|
|
new = g_new0(tree_entry, 1);
|
|
if (!current) {
|
|
/* Append to the end of the list */
|
|
if (!ts.tree_first) {
|
|
/* Empty list */
|
|
ts.tree_first = new;
|
|
new->prev = NULL;
|
|
} else {
|
|
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) {
|
|
/* 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 = g_strdup(name);
|
|
len = strlen(new->name);
|
|
new->sublevel = 0;
|
|
for (i = 0; i < len; i++)
|
|
if (new->name[i] == PATH_SEP) {
|
|
new->sublevel++;
|
|
new->subname = new->name + i + 1;
|
|
}
|
|
if (new->next)
|
|
submask = new->next->submask;
|
|
else
|
|
submask = 0;
|
|
submask |= 1 << new->sublevel;
|
|
submask &= (2 << new->sublevel) - 1;
|
|
new->submask = submask;
|
|
new->mark = 0;
|
|
|
|
/* Correct the submasks of the previous entries */
|
|
current = new->prev;
|
|
while (current && current->sublevel > new->sublevel) {
|
|
current->submask |= 1 << new->sublevel;
|
|
current = current->prev;
|
|
}
|
|
|
|
/* The entry has now been added */
|
|
|
|
if (new->sublevel > 1) {
|
|
/* Let's check if the parent directory is in the tree */
|
|
char *parent = g_strdup(new->name);
|
|
int i;
|
|
|
|
for (i = strlen(parent) - 1; i > 1; i--) {
|
|
if (parent[i] == PATH_SEP) {
|
|
parent[i] = 0;
|
|
tree_store_add_entry(parent);
|
|
break;
|
|
}
|
|
}
|
|
g_free(parent);
|
|
}
|
|
|
|
tree_store_dirty(TRUE);
|
|
return new;
|
|
}
|
|
|
|
static Hook *remove_entry_hooks;
|
|
|
|
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);
|
|
}
|
|
|
|
static void
|
|
tree_store_notify_remove(tree_entry * entry)
|
|
{
|
|
Hook *p = remove_entry_hooks;
|
|
tree_store_remove_fn r;
|
|
|
|
while (p) {
|
|
r = (tree_store_remove_fn) p->hook_fn;
|
|
r(entry, p->hook_data);
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
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)
|
|
submask = entry->next->submask;
|
|
while (current && current->sublevel > entry->sublevel) {
|
|
submask |= 1 << current->sublevel;
|
|
submask &= (2 << current->sublevel) - 1;
|
|
current->submask = submask;
|
|
current = current->prev;
|
|
}
|
|
|
|
/* Unlink the entry from the list */
|
|
if (entry->prev)
|
|
entry->prev->next = entry->next;
|
|
else
|
|
ts.tree_first = entry->next;
|
|
|
|
if (entry->next)
|
|
entry->next->prev = entry->prev;
|
|
else
|
|
ts.tree_last = entry->prev;
|
|
|
|
/* Free the memory used by the entry */
|
|
g_free(entry->name);
|
|
g_free(entry);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
tree_store_remove_entry(const char *name)
|
|
{
|
|
tree_entry *current, *base, *old;
|
|
int len;
|
|
|
|
g_return_if_fail(name != NULL);
|
|
|
|
/* Miguel Ugly hack */
|
|
if (name[0] == PATH_SEP && name[1] == 0)
|
|
return;
|
|
/* Miguel Ugly hack end */
|
|
|
|
base = tree_store_whereis(name);
|
|
if (!base)
|
|
return; /* Doesn't exist */
|
|
|
|
len = strlen(base->name);
|
|
current = base->next;
|
|
while (current
|
|
&& strncmp(current->name, base->name, len) == 0
|
|
&& (current->name[len] == '\0'
|
|
|| current->name[len] == PATH_SEP)) {
|
|
old = current;
|
|
current = current->next;
|
|
remove_entry(old);
|
|
}
|
|
remove_entry(base);
|
|
tree_store_dirty(TRUE);
|
|
|
|
return;
|
|
}
|
|
|
|
/* This subdirectory exists -> clear deletion mark */
|
|
void
|
|
tree_store_mark_checked(const char *subname)
|
|
{
|
|
char *name;
|
|
tree_entry *current, *base;
|
|
int flag = 1, len;
|
|
if (!ts.loaded)
|
|
return;
|
|
|
|
if (ts.check_name == NULL)
|
|
return;
|
|
|
|
/* Calculate the full name of the subdirectory */
|
|
if (subname[0] == '.' &&
|
|
(subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
|
|
return;
|
|
if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
|
|
name = g_strconcat(PATH_SEP_STR, subname, (char *) NULL);
|
|
else
|
|
name = concat_dir_and_file(ts.check_name, subname);
|
|
|
|
/* Search for the subdirectory */
|
|
current = ts.check_start;
|
|
while (current && (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 = g_list_prepend(ts.add_queue, g_strdup(name));
|
|
}
|
|
g_free(name);
|
|
|
|
/* Clear the deletion mark from the subdirectory and its children */
|
|
base = current;
|
|
if (base) {
|
|
len = strlen(base->name);
|
|
base->mark = 0;
|
|
current = base->next;
|
|
while (current
|
|
&& strncmp(current->name, base->name, len) == 0
|
|
&& (current->name[len] == '\0'
|
|
|| current->name[len] == PATH_SEP || len == 1)) {
|
|
current->mark = 0;
|
|
current = current->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Mark the subdirectories of the current directory for delete */
|
|
tree_entry *
|
|
tree_store_start_check(const char *path)
|
|
{
|
|
tree_entry *current, *retval;
|
|
int 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(path);
|
|
if (!current) {
|
|
struct stat s;
|
|
|
|
if (mc_stat(path, &s) == -1)
|
|
return NULL;
|
|
|
|
if (!S_ISDIR(s.st_mode))
|
|
return NULL;
|
|
|
|
current = tree_store_add_entry(path);
|
|
ts.check_name = g_strdup(path);
|
|
|
|
return current;
|
|
}
|
|
|
|
ts.check_name = g_strdup(path);
|
|
|
|
retval = current;
|
|
|
|
/* Mark old subdirectories for delete */
|
|
ts.check_start = current->next;
|
|
len = strlen(ts.check_name);
|
|
|
|
current = ts.check_start;
|
|
while (current
|
|
&& strncmp(current->name, ts.check_name, len) == 0
|
|
&& (current->name[len] == '\0' || current->name[len] == PATH_SEP
|
|
|| len == 1)) {
|
|
current->mark = 1;
|
|
current = current->next;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Delete subdirectories which still have the deletion mark */
|
|
void
|
|
tree_store_end_check(void)
|
|
{
|
|
tree_entry *current, *old;
|
|
int len;
|
|
GList *the_queue, *l;
|
|
|
|
if (!ts.loaded)
|
|
return;
|
|
|
|
g_return_if_fail(ts.check_name != NULL);
|
|
|
|
/* Check delete marks and delete if found */
|
|
len = strlen(ts.check_name);
|
|
|
|
current = ts.check_start;
|
|
while (current
|
|
&& strncmp(current->name, ts.check_name, len) == 0
|
|
&& (current->name[len] == '\0' || current->name[len] == PATH_SEP
|
|
|| len == 1)) {
|
|
old = current;
|
|
current = current->next;
|
|
if (old->mark)
|
|
remove_entry(old);
|
|
}
|
|
|
|
/* get the stuff in the scan order */
|
|
ts.add_queue = g_list_reverse(ts.add_queue);
|
|
the_queue = ts.add_queue;
|
|
ts.add_queue = NULL;
|
|
g_free(ts.check_name);
|
|
ts.check_name = NULL;
|
|
|
|
for (l = the_queue; l; l = l->next) {
|
|
g_free(l->data);
|
|
}
|
|
|
|
g_list_free(the_queue);
|
|
}
|
|
|
|
static void
|
|
process_special_dirs(GList ** special_dirs, char *file)
|
|
{
|
|
char *token;
|
|
char *buffer = g_malloc(4096);
|
|
char *s;
|
|
|
|
GetPrivateProfileString("Special dirs", "list",
|
|
"", buffer, 4096, file);
|
|
s = buffer;
|
|
while ((token = strtok(s, ",")) != NULL) {
|
|
*special_dirs = g_list_prepend(*special_dirs, g_strdup(token));
|
|
s = NULL;
|
|
}
|
|
g_free(buffer);
|
|
}
|
|
|
|
static gboolean
|
|
should_skip_directory(const char *dir)
|
|
{
|
|
static GList *special_dirs;
|
|
GList *l;
|
|
static int loaded;
|
|
|
|
if (loaded == 0) {
|
|
loaded = 1;
|
|
setup_init();
|
|
process_special_dirs(&special_dirs, profile_name);
|
|
process_special_dirs(&special_dirs, global_profile_name);
|
|
}
|
|
|
|
for (l = special_dirs; l; l = l->next) {
|
|
if (strncmp(dir, l->data, strlen(l->data)) == 0)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
tree_entry *
|
|
tree_store_rescan(const char *dir)
|
|
{
|
|
DIR *dirp;
|
|
struct dirent *dp;
|
|
struct stat buf;
|
|
tree_entry *entry;
|
|
|
|
if (should_skip_directory(dir)) {
|
|
entry = tree_store_add_entry(dir);
|
|
entry->scanned = 1;
|
|
|
|
return entry;
|
|
}
|
|
|
|
entry = tree_store_start_check(dir);
|
|
|
|
if (!entry)
|
|
return NULL;
|
|
|
|
dirp = mc_opendir(dir);
|
|
if (dirp) {
|
|
for (dp = mc_readdir(dirp); dp; dp = mc_readdir(dirp)) {
|
|
char *full_name;
|
|
|
|
if (dp->d_name[0] == '.') {
|
|
if (dp->d_name[1] == 0
|
|
|| (dp->d_name[1] == '.' && dp->d_name[2] == 0))
|
|
continue;
|
|
}
|
|
|
|
full_name = concat_dir_and_file(dir, dp->d_name);
|
|
if (mc_lstat(full_name, &buf) != -1) {
|
|
if (S_ISDIR(buf.st_mode))
|
|
tree_store_mark_checked(dp->d_name);
|
|
}
|
|
g_free(full_name);
|
|
}
|
|
mc_closedir(dirp);
|
|
}
|
|
tree_store_end_check();
|
|
entry->scanned = 1;
|
|
|
|
return entry;
|
|
}
|