mc/src/treestore.c
Timur Bakeyev a39568367e Glibing..... (2)
Wed Jan 27 03:17:44 1999  Timur Bakeyev <mc@bat.ru>

	* Converted memory managment to Glib. Now we use g_new()/g_malloc()/
	g_strdup()/g_free() routings. Also, copy_strings() replaced by
	g_strconcat(), strcasecmp() -> g_strcasecmp(),and sprintf() by
	g_snprintf().

	* Some sequences of malloc()/sprintf() changed to g_strdup_printf().

	* mad.[ch]: Modified, to work with new GLib's memory managment. Fixed
	a missing #undef for tempnam, which caused dead loop. Add several new
	functions to emulate GLib memory managment.

	*main.c, mad.[ch]: Add a new switch  "-M", which allows to redirect MAD
	messages to the file.

	* util.[ch], utilunix.c: Modified, deleted our variants of strcasecmp()
	and strdup() - we have g_ equivalences. Remove get_full_name() - it is
	similar to concat_dir_and_file(). Some other tricks with g_* functions.

	* global.h: Modified, extended. Now it is main memory mangment include -
	i.e. all inclusions of <stdlib.h>, <malloc.h>, <glib.h>, "fs.h", "mem.h",
	"util.h" and "mad.h" done there. This elimanates problem with proper or-
	der of #include's.

	* All around the source - changed order of #include's, most of them gone
	to global.h (see above), minor changes, like "0" -> NULL in string func-
	tions.
1999-01-27 01:08:30 +00:00

817 lines
16 KiB
C

/*
* Tree Store
*
* Contains a storage of the file system tree representation
*
Copyright (C) 1994, 1995, 1996, 1997 The Free Software Foundation
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., 675 Mass Ave, Cambridge, MA 02139, 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 <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <errno.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include "global.h"
#include "treestore.h"
#include "../vfs/vfs.h"
#ifdef OS2_NT
# include <io.h>
#endif
#define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
static TreeStore ts;
void (*tree_store_dirty_notify)(int state) = NULL;
void
tree_store_dirty (int state)
{
ts.dirty = state;
if (tree_store_dirty_notify)
(*tree_store_dirty_notify)(state);
}
/* Returns number of common characters */
static int str_common (char *s1, char *s2)
{
int result = 0;
while (*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 (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;
}
TreeStore *
tree_store_init (void)
{
ts.tree_first = 0;
ts.tree_last = 0;
ts.refcount++;
return &ts;
}
void
tree_store_destroy (void)
{
tree_entry *current, *old;
ts.refcount--;
current = ts.tree_first;
while (current){
old = current;
current = current->next;
g_free (old->name);
g_free (old);
}
ts.tree_first = NULL;
ts.tree_last = NULL;
ts.loaded = FALSE;
}
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;
}
int
tree_store_load (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, TRUE);
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);
#ifdef OS2_NT
/* .ado: Drives for NT and OS/2 */
if ((len > 2) &&
isalpha(name[0]) &&
(name[1] == ':') &&
(name[2] == '\\')) {
tree_store_add_entry (name);
strcpy (oldname, name);
} else
#endif
/* UNIX Version */
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);
e = tree_store_add_entry (oldname);
e->scanned = scanned;
}
}
} else {
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;
}
static char *
encode (char *string)
{
int special_chars;
char *p, *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;
}
int
tree_store_save (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;
/* 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, _("Can't 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_entry *
tree_store_add_entry (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_new (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;
}
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 (char *name)
{
tree_entry *current, *base, *old;
int len, base_sublevel;
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 */
if (ts.check_name [0] == PATH_SEP && ts.check_name [1] == 0)
base_sublevel = base->sublevel;
else
base_sublevel = base->sublevel + 1;
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;
g_return_if_fail (ts.check_name != NULL);
/* 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, 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);
tree_store_notify_add (current);
}
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 (char *path)
{
tree_entry *current, *retval;
int len;
if (!ts.loaded)
return 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;
}
tree_entry *
tree_store_start_check_cwd (void)
{
char buffer [MC_MAXPATHLEN];
mc_get_current_wd (buffer, MC_MAXPATHLEN);
return tree_store_start_check (buffer);
}
/* Delete subdirectories which still have the deletion mark */
void
tree_store_end_check (void)
{
tree_entry *current, *old;
int len;
if (!ts.loaded)
return;
g_return_if_fail (ts.check_name);
/* 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);
}
g_free (ts.check_name);
ts.check_name = NULL;
}
tree_entry *
tree_store_rescan (char *dir)
{
DIR *dirp;
struct dirent *dp;
struct stat buf;
tree_entry *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] == '.' && 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;
}
static Hook *remove_entry_hooks;
static Hook *add_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);
}
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;
}
}
void
tree_store_add_entry_add_hook (tree_store_add_fn callback, void *data)
{
add_hook (&add_entry_hooks, (void (*)(void *))callback, data);
}
void
tree_store_remove_entry_add_hook (tree_store_add_fn callback)
{
delete_hook (&add_entry_hooks, (void (*)(void *))callback);
}
void
tree_store_notify_add (tree_entry *entry)
{
Hook *p = add_entry_hooks;
tree_store_add_fn r;
while (p){
r = (tree_store_add_fn) p->hook_fn;
r (entry, p->hook_data);
p = p->next;
}
}
tree_scan *
tree_store_opendir (char *path)
{
tree_entry *entry;
tree_scan *scan;
entry = tree_store_whereis (path);
if (!entry || (entry && !entry->scanned)){
entry = tree_store_rescan (path);
if (!entry)
return NULL;
}
if (entry->next == NULL)
return NULL;
scan = g_new (tree_scan, 1);
scan->base = entry;
scan->current = entry->next;
scan->sublevel = entry->next->sublevel;
scan->base_dir_len = strlen (path);
return scan;
}
tree_entry *
tree_store_readdir (tree_scan *scan)
{
tree_entry *entry;
int len;
g_assert (scan != NULL);
len = scan->base_dir_len;
entry = scan->current;
while (entry &&
(strncmp (entry->name, scan->base->name, len) == 0) &&
(entry->name [len] == 0 || entry->name [len] == PATH_SEP || len == 1)){
if (entry->sublevel == scan->sublevel){
scan->current = entry->next;
return entry;
}
entry = entry->next;
}
return NULL;
}
void
tree_store_closedir (tree_scan *scanner)
{
g_assert (scanner != NULL);
g_free (scanner);
}