* vfs.c (vfs_init) [!WITH_MCFS]: Don't register mcfs.
[midnight-commander.git] / src / treestore.c
blobea718f507d8b349cbde707ec4a342d0298dcf71f
1 /*
2 * Tree Store
4 * Contains a storage of the file system tree representation
6 Copyright (C) 1994, 1995, 1996, 1997 The Free Software Foundation
8 Written: 1994, 1996 Janne Kukonlehto
9 1997 Norbert Warmuth
10 1996, 1999 Miguel de Icaza
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 This module has been converted to be a widget.
28 The program load and saves the tree each time the tree widget is
29 created and destroyed. This is required for the future vfs layer,
30 it will be possible to have tree views over virtual file systems.
33 #include <config.h>
34 #include <sys/types.h>
35 #ifdef HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38 #include <fcntl.h>
39 #include <sys/stat.h>
40 #include <errno.h>
41 #include <stdio.h>
42 #include <string.h>
44 #include "global.h"
45 #include "treestore.h"
46 #include "../vfs/vfs.h"
47 #include "profile.h"
48 #include "setup.h"
50 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
52 static TreeStore ts;
54 void (*tree_store_dirty_notify)(int state) = NULL;
56 static void
57 tree_store_dirty (int state)
59 ts.dirty = state;
61 if (tree_store_dirty_notify)
62 (*tree_store_dirty_notify)(state);
65 /* Returns number of common characters */
66 static int str_common (char *s1, char *s2)
68 int result = 0;
70 while (*s1++ == *s2++)
71 result++;
72 return result;
75 /* The directory names are arranged in a single linked list in the same
76 order as they are displayed. When the tree is displayed the expected
77 order is like this:
79 /bin
80 /etc
81 /etc/X11
82 /etc/rc.d
83 /etc.old/X11
84 /etc.old/rc.d
85 /usr
87 i.e. the required collating sequence when comparing two directory names is
88 '\0' < PATH_SEP < all-other-characters-in-encoding-order
90 Since strcmp doesn't fulfil this requirement we use pathcmp when
91 inserting directory names into the list. The meaning of the return value
92 of pathcmp and strcmp are the same (an integer less than, equal to, or
93 greater than zero if p1 is found to be less than, to match, or be greater
94 than p2.
96 static int
97 pathcmp (const char *p1, const char *p2)
99 for ( ;*p1 == *p2; p1++, p2++)
100 if (*p1 == '\0' )
101 return 0;
103 if (*p1 == '\0')
104 return -1;
105 if (*p2 == '\0')
106 return 1;
107 if (*p1 == PATH_SEP)
108 return -1;
109 if (*p2 == PATH_SEP)
110 return 1;
111 return (*p1 - *p2);
114 /* Searches for specified directory */
115 tree_entry *
116 tree_store_whereis (char *name)
118 tree_entry *current = ts.tree_first;
119 int flag = -1;
121 while (current && (flag = pathcmp (current->name, name)) < 0)
122 current = current->next;
124 if (flag == 0)
125 return current;
126 else
127 return NULL;
130 TreeStore *
131 tree_store_get (void)
133 return &ts;
136 static char *
137 decode (char *buffer)
139 char *res = g_strdup (buffer);
140 char *p, *q;
142 for (p = q = res; *p; p++, q++){
143 if (*p == '\n'){
144 *q = 0;
145 return res;
148 if (*p != '\\'){
149 *q = *p;
150 continue;
153 p++;
155 switch (*p){
156 case 'n':
157 *q = '\n';
158 break;
159 case '\\':
160 *q = '\\';
161 break;
164 *q = *p;
166 return res;
169 /* Loads the tree store from the specified filename */
170 static int
171 tree_store_load_from (char *name)
173 FILE *file;
174 char buffer [MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
175 char *different;
176 int len, common;
177 int do_load;
179 g_return_val_if_fail (name != NULL, FALSE);
181 if (ts.loaded)
182 return TRUE;
184 file = fopen (name, "r");
186 if (file){
187 fgets (buffer, sizeof (buffer), file);
189 if (strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0){
190 fclose (file);
191 do_load = FALSE;
192 } else
193 do_load = TRUE;
194 } else
195 do_load = FALSE;
197 if (do_load){
198 ts.loaded = TRUE;
200 /* File open -> read contents */
201 oldname [0] = 0;
202 while (fgets (buffer, MC_MAXPATHLEN, file)){
203 tree_entry *e;
204 int scanned;
205 char *name;
207 /* Skip invalid records */
208 if ((buffer [0] != '0' && buffer [0] != '1'))
209 continue;
211 if (buffer [1] != ':')
212 continue;
214 scanned = buffer [0] == '1';
216 name = decode (buffer+2);
218 len = strlen (name);
219 #ifdef OS2_NT
220 /* .ado: Drives for NT and OS/2 */
221 if ((len > 2) &&
222 isalpha(name[0]) &&
223 (name[1] == ':') &&
224 (name[2] == '\\')) {
225 tree_store_add_entry (name);
226 strcpy (oldname, name);
227 } else
228 #endif
229 /* UNIX Version */
230 if (name [0] != PATH_SEP){
231 /* Clear-text decompression */
232 char *s = strtok (name, " ");
234 if (s){
235 common = atoi (s);
236 different = strtok (NULL, "");
237 if (different){
238 strcpy (oldname + common, different);
239 if (vfs_file_is_local (oldname)){
240 e = tree_store_add_entry (oldname);
241 e->scanned = scanned;
245 } else {
246 if (vfs_file_is_local (name)){
247 e = tree_store_add_entry (name);
248 e->scanned = scanned;
250 strcpy (oldname, name);
252 g_free (name);
254 fclose (file);
257 /* Nothing loaded, we add some standard directories */
258 if (!ts.tree_first){
259 tree_store_add_entry (PATH_SEP_STR);
260 tree_store_rescan (PATH_SEP_STR);
261 ts.loaded = TRUE;
264 return TRUE;
268 * tree_store_load:
269 * @void:
271 * Loads the tree from the default location.
273 * Return value: TRUE if success, FALSE otherwise.
276 tree_store_load (void)
278 char *name;
279 int retval;
281 name = concat_dir_and_file (home_dir, MC_TREE);
282 retval = tree_store_load_from (name);
283 g_free (name);
285 return retval;
288 static char *
289 encode (char *string)
291 int special_chars;
292 char *p, *q;
293 char *res;
295 for (special_chars = 0, p = string; *p; p++){
296 if (*p == '\n' || *p == '\\')
297 special_chars++;
300 res = g_malloc (p - string + special_chars + 1);
301 for (p = string, q = res; *p; p++, q++){
302 if (*p != '\n' && *p != '\\'){
303 *q = *p;
304 continue;
307 *q++ = '\\';
309 switch (*p){
310 case '\n':
311 *q = 'n';
312 break;
314 case '\\':
315 *q = '\\';
316 break;
319 *q = 0;
320 return res;
323 /* Saves the tree to the specified filename */
324 static int
325 tree_store_save_to (char *name)
327 tree_entry *current;
328 FILE *file;
330 file = fopen (name, "w");
331 if (!file)
332 return errno;
334 fprintf (file, "%s\n", TREE_SIGNATURE);
336 current = ts.tree_first;
337 while (current){
338 int i, common;
340 if (vfs_file_is_local (current->name)){
341 /* Clear-text compression */
342 if (current->prev
343 && (common = str_common (current->prev->name, current->name)) > 2){
344 char *encoded = encode (current->name + common);
346 i = fprintf (file, "%d:%d %s\n", current->scanned, common, encoded);
347 g_free (encoded);
348 } else {
349 char *encoded = encode (current->name);
351 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
352 g_free (encoded);
355 if (i == EOF){
356 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"), name,
357 unix_error_string (errno));
358 break;
361 current = current->next;
363 tree_store_dirty (FALSE);
364 fclose (file);
366 return 0;
370 * tree_store_save:
371 * @void:
373 * Saves the tree to the default file in an atomic fashion.
375 * Return value: 0 if success, errno on error.
378 tree_store_save (void)
380 char *tmp;
381 char *name;
382 int retval;
384 tmp = concat_dir_and_file (home_dir, MC_TREE_TMP);
385 retval = tree_store_save_to (tmp);
387 if (retval) {
388 g_free (tmp);
389 return retval;
392 name = concat_dir_and_file (home_dir, MC_TREE);
393 retval = rename (tmp, name);
395 g_free (tmp);
396 g_free (name);
398 if (retval)
399 return errno;
400 else
401 return 0;
404 tree_entry *
405 tree_store_add_entry (char *name)
407 int flag = -1;
408 tree_entry *current = ts.tree_first;
409 tree_entry *old = NULL;
410 tree_entry *new;
411 int i, len;
412 int submask = 0;
414 if (ts.tree_last && ts.tree_last->next)
415 abort ();
417 /* Search for the correct place */
418 while (current && (flag = pathcmp (current->name, name)) < 0){
419 old = current;
420 current = current->next;
423 if (flag == 0)
424 return current; /* Already in the list */
426 /* Not in the list -> add it */
427 new = g_new0 (tree_entry, 1);
428 if (!current){
429 /* Append to the end of the list */
430 if (!ts.tree_first){
431 /* Empty list */
432 ts.tree_first = new;
433 new->prev = NULL;
434 } else {
435 old->next = new;
436 new->prev = old;
438 new->next = NULL;
439 ts.tree_last = new;
440 } else {
441 /* Insert in to the middle of the list */
442 new->prev = old;
443 if (old){
444 /* Yes, in the middle */
445 new->next = old->next;
446 old->next = new;
447 } else {
448 /* Nope, in the beginning of the list */
449 new->next = ts.tree_first;
450 ts.tree_first = new;
452 new->next->prev = new;
455 /* Calculate attributes */
456 new->name = g_strdup (name);
457 len = strlen (new->name);
458 new->sublevel = 0;
459 for (i = 0; i < len; i++)
460 if (new->name [i] == PATH_SEP){
461 new->sublevel++;
462 new->subname = new->name + i + 1;
464 if (new->next)
465 submask = new->next->submask;
466 else
467 submask = 0;
468 submask |= 1 << new->sublevel;
469 submask &= (2 << new->sublevel) - 1;
470 new->submask = submask;
471 new->mark = 0;
473 /* Correct the submasks of the previous entries */
474 current = new->prev;
475 while (current && current->sublevel > new->sublevel){
476 current->submask |= 1 << new->sublevel;
477 current = current->prev;
480 /* The entry has now been added */
482 if (new->sublevel > 1){
483 /* Let's check if the parent directory is in the tree */
484 char *parent = g_strdup (new->name);
485 int i;
487 for (i = strlen (parent) - 1; i > 1; i--){
488 if (parent [i] == PATH_SEP){
489 parent [i] = 0;
490 tree_store_add_entry (parent);
491 break;
494 g_free (parent);
497 tree_store_dirty (TRUE);
498 return new;
501 static tree_entry *
502 remove_entry (tree_entry *entry)
504 tree_entry *current = entry->prev;
505 long submask = 0;
506 tree_entry *ret = NULL;
508 tree_store_notify_remove (entry);
510 /* Correct the submasks of the previous entries */
511 if (entry->next)
512 submask = entry->next->submask;
513 while (current && current->sublevel > entry->sublevel){
514 submask |= 1 << current->sublevel;
515 submask &= (2 << current->sublevel) - 1;
516 current->submask = submask;
517 current = current->prev;
520 /* Unlink the entry from the list */
521 if (entry->prev)
522 entry->prev->next = entry->next;
523 else
524 ts.tree_first = entry->next;
526 if (entry->next)
527 entry->next->prev = entry->prev;
528 else
529 ts.tree_last = entry->prev;
531 /* Free the memory used by the entry */
532 g_free (entry->name);
533 g_free (entry);
535 return ret;
538 void
539 tree_store_remove_entry (char *name)
541 tree_entry *current, *base, *old;
542 int len, base_sublevel;
544 g_return_if_fail (name != NULL);
545 g_return_if_fail (ts.check_name != NULL);
547 /* Miguel Ugly hack */
548 if (name [0] == PATH_SEP && name [1] == 0)
549 return;
550 /* Miguel Ugly hack end */
552 base = tree_store_whereis (name);
553 if (!base)
554 return; /* Doesn't exist */
556 if (ts.check_name [0] == PATH_SEP && ts.check_name [1] == 0)
557 base_sublevel = base->sublevel;
558 else
559 base_sublevel = base->sublevel + 1;
561 len = strlen (base->name);
562 current = base->next;
563 while (current
564 && strncmp (current->name, base->name, len) == 0
565 && (current->name[len] == '\0' || current->name[len] == PATH_SEP)) {
566 old = current;
567 current = current->next;
568 remove_entry (old);
570 remove_entry (base);
571 tree_store_dirty (TRUE);
573 return;
576 /* This subdirectory exists -> clear deletion mark */
577 void
578 tree_store_mark_checked (const char *subname)
580 char *name;
581 tree_entry *current, *base;
582 int flag = 1, len;
583 if (!ts.loaded)
584 return;
586 if (ts.check_name == NULL)
587 return;
589 /* Calculate the full name of the subdirectory */
590 if (subname [0] == '.' &&
591 (subname [1] == 0 || (subname [1] == '.' && subname [2] == 0)))
592 return;
593 if (ts.check_name [0] == PATH_SEP && ts.check_name [1] == 0)
594 name = g_strconcat (PATH_SEP_STR, subname, NULL);
595 else
596 name = concat_dir_and_file (ts.check_name, subname);
598 /* Search for the subdirectory */
599 current = ts.check_start;
600 while (current && (flag = pathcmp (current->name, name)) < 0)
601 current = current->next;
603 if (flag != 0){
604 /* Doesn't exist -> add it */
605 current = tree_store_add_entry (name);
606 ts.add_queue = g_list_prepend (ts.add_queue, g_strdup (name));
608 g_free (name);
610 /* Clear the deletion mark from the subdirectory and its children */
611 base = current;
612 if (base){
613 len = strlen (base->name);
614 base->mark = 0;
615 current = base->next;
616 while (current
617 && strncmp (current->name, base->name, len) == 0
618 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1)){
619 current->mark = 0;
620 current = current->next;
625 /* Mark the subdirectories of the current directory for delete */
626 tree_entry *
627 tree_store_start_check (char *path)
629 tree_entry *current, *retval;
630 int len;
632 if (!ts.loaded)
633 return NULL;
635 g_return_val_if_fail (ts.check_name == NULL, NULL);
636 ts.check_start = NULL;
638 tree_store_set_freeze (TRUE);
640 /* Search for the start of subdirectories */
641 current = tree_store_whereis (path);
642 if (!current){
643 struct stat s;
645 if (mc_stat (path, &s) == -1)
646 return NULL;
648 if (!S_ISDIR (s.st_mode))
649 return NULL;
651 current = tree_store_add_entry (path);
652 ts.check_name = g_strdup (path);
654 return current;
657 ts.check_name = g_strdup (path);
659 retval = current;
661 /* Mark old subdirectories for delete */
662 ts.check_start = current->next;
663 len = strlen (ts.check_name);
665 current = ts.check_start;
666 while (current
667 && strncmp (current->name, ts.check_name, len) == 0
668 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1)){
669 current->mark = 1;
670 current = current->next;
673 return retval;
676 tree_entry *
677 tree_store_start_check_cwd (void)
679 char buffer [MC_MAXPATHLEN];
681 mc_get_current_wd (buffer, MC_MAXPATHLEN);
682 return tree_store_start_check (buffer);
685 /* Delete subdirectories which still have the deletion mark */
686 void
687 tree_store_end_check (void)
689 tree_entry *current, *old;
690 int len;
691 GList *the_queue, *l;
693 if (!ts.loaded)
694 return;
696 g_return_if_fail (ts.check_name != NULL);
698 /* Check delete marks and delete if found */
699 len = strlen (ts.check_name);
701 current = ts.check_start;
702 while (current
703 && strncmp (current->name, ts.check_name, len) == 0
704 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1)){
705 old = current;
706 current = current->next;
707 if (old->mark)
708 remove_entry (old);
711 /* get the stuff in the scan order */
712 ts.add_queue = g_list_reverse (ts.add_queue);
713 the_queue = ts.add_queue;
714 ts.add_queue = NULL;
715 g_free (ts.check_name);
716 ts.check_name = NULL;
718 for (l = the_queue; l; l = l->next){
719 tree_store_notify_add (l->data);
720 g_free (l->data);
723 g_list_free (the_queue);
725 tree_store_set_freeze (FALSE);
728 static void
729 process_special_dirs (GList **special_dirs, char *file)
731 char *token;
732 char *buffer = g_malloc (4096);
733 char *s;
735 GetPrivateProfileString ("Special dirs", "list",
736 "", buffer, 4096, file);
737 s = buffer;
738 while ((token = strtok (s, ",")) != NULL){
739 *special_dirs = g_list_prepend (*special_dirs, g_strdup (token));
740 s = NULL;
742 g_free (buffer);
745 static gboolean
746 should_skip_directory (char *dir)
748 static GList *special_dirs;
749 GList *l;
750 static int loaded;
752 if (loaded == 0){
753 loaded = 1;
754 setup_init ();
755 process_special_dirs (&special_dirs, profile_name);
756 process_special_dirs (&special_dirs, CONFDIR "mc.global");
759 for (l = special_dirs; l; l = l->next){
760 if (strncmp (dir, l->data, strlen (l->data)) == 0)
761 return TRUE;
763 return FALSE;
766 tree_entry *
767 tree_store_rescan (char *dir)
769 DIR *dirp;
770 struct dirent *dp;
771 struct stat buf;
772 tree_entry *entry;
774 if (should_skip_directory (dir)){
775 entry = tree_store_add_entry (dir);
776 entry->scanned = 1;
778 return entry;
781 entry = tree_store_start_check (dir);
783 if (!entry)
784 return NULL;
786 dirp = mc_opendir (dir);
787 if (dirp){
788 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp)){
789 char *full_name;
791 if (dp->d_name [0] == '.'){
792 if (dp->d_name [1] == 0
793 || (dp->d_name [1] == '.' && dp->d_name [2] == 0))
794 continue;
797 full_name = concat_dir_and_file (dir, dp->d_name);
798 if (mc_lstat (full_name, &buf) != -1){
799 if (S_ISDIR (buf.st_mode))
800 tree_store_mark_checked (dp->d_name);
802 g_free (full_name);
804 mc_closedir (dirp);
806 tree_store_end_check ();
807 entry->scanned = 1;
809 return entry;
812 static Hook *remove_entry_hooks;
813 static Hook *add_entry_hooks;
814 static Hook *freeze_hooks;
816 void
817 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
819 add_hook (&remove_entry_hooks, (void (*)(void *))callback, data);
822 void
823 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
825 delete_hook (&remove_entry_hooks, (void (*)(void *))callback);
828 void
829 tree_store_notify_remove (tree_entry *entry)
831 Hook *p = remove_entry_hooks;
832 tree_store_remove_fn r;
835 while (p){
836 r = (tree_store_remove_fn) p->hook_fn;
837 r (entry, p->hook_data);
838 p = p->next;
841 void
842 tree_store_add_entry_add_hook (tree_store_add_fn callback, void *data)
844 add_hook (&add_entry_hooks, (void (*)(void *))callback, data);
847 void
848 tree_store_remove_entry_add_hook (tree_store_add_fn callback)
850 delete_hook (&add_entry_hooks, (void (*)(void *))callback);
853 void
854 tree_store_notify_add (char *directory)
856 Hook *p = add_entry_hooks;
857 tree_store_add_fn r;
859 while (p) {
860 r = (tree_store_add_fn) p->hook_fn;
861 r (directory, p->hook_data);
862 p = p->next;
866 void
867 tree_store_add_freeze_hook (tree_freeze_fn callback, void *data)
869 add_hook (&freeze_hooks, (void (*)(void *)) callback, data);
872 void
873 tree_store_remove_freeze_hook (tree_freeze_fn callback)
875 delete_hook (&freeze_hooks, (void (*)(void *))callback);
878 void
879 tree_store_set_freeze (int freeze)
881 Hook *p = freeze_hooks;
882 tree_freeze_fn f;
884 while (p) {
885 f = (tree_freeze_fn) p->hook_fn;
886 f (freeze, p->hook_data);
887 p = p->next;
891 tree_scan *
892 tree_store_opendir (char *path)
894 tree_entry *entry;
895 tree_scan *scan;
897 entry = tree_store_whereis (path);
898 if (!entry || (entry && !entry->scanned)) {
899 entry = tree_store_rescan (path);
901 if (!entry)
902 return NULL;
905 if (entry->next == NULL)
906 return NULL;
908 scan = g_new (tree_scan, 1);
909 scan->base = entry;
910 scan->current = entry->next;
911 scan->sublevel = entry->next->sublevel;
913 scan->base_dir_len = strlen (path);
914 return scan;
917 tree_entry *
918 tree_store_readdir (tree_scan *scan)
920 tree_entry *entry;
921 int len;
923 g_assert (scan != NULL);
925 len = scan->base_dir_len;
926 entry = scan->current;
927 while (entry &&
928 (strncmp (entry->name, scan->base->name, len) == 0) &&
929 (entry->name [len] == 0 || entry->name [len] == PATH_SEP || len == 1)){
931 if (entry->sublevel == scan->sublevel){
932 scan->current = entry->next;
933 return entry;
935 entry = entry->next;
938 return NULL;
941 void
942 tree_store_closedir (tree_scan *scanner)
944 g_assert (scanner != NULL);
946 g_free (scanner);