*** empty log message ***
[midnight-commander.git] / src / treestore.c
blob85f2646b2d90a371f6d220ea35c722f43d048500
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 <dirent.h>
42 #include <stdio.h>
43 #include <string.h>
45 #include "global.h"
46 #include "treestore.h"
47 #include "../vfs/vfs.h"
48 #include "profile.h"
49 #include "setup.h"
51 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
53 static TreeStore ts;
55 void (*tree_store_dirty_notify)(int state) = NULL;
57 static void
58 tree_store_dirty (int state)
60 ts.dirty = state;
62 if (tree_store_dirty_notify)
63 (*tree_store_dirty_notify)(state);
66 /* Returns number of common characters */
67 static int str_common (char *s1, char *s2)
69 int result = 0;
71 while (*s1++ == *s2++)
72 result++;
73 return result;
76 /* The directory names are arranged in a single linked list in the same
77 order as they are displayed. When the tree is displayed the expected
78 order is like this:
80 /bin
81 /etc
82 /etc/X11
83 /etc/rc.d
84 /etc.old/X11
85 /etc.old/rc.d
86 /usr
88 i.e. the required collating sequence when comparing two directory names is
89 '\0' < PATH_SEP < all-other-characters-in-encoding-order
91 Since strcmp doesn't fulfil this requirement we use pathcmp when
92 inserting directory names into the list. The meaning of the return value
93 of pathcmp and strcmp are the same (an integer less than, equal to, or
94 greater than zero if p1 is found to be less than, to match, or be greater
95 than p2.
97 static int
98 pathcmp (const char *p1, const char *p2)
100 for ( ;*p1 == *p2; p1++, p2++)
101 if (*p1 == '\0' )
102 return 0;
104 if (*p1 == '\0')
105 return -1;
106 if (*p2 == '\0')
107 return 1;
108 if (*p1 == PATH_SEP)
109 return -1;
110 if (*p2 == PATH_SEP)
111 return 1;
112 return (*p1 - *p2);
115 /* Searches for specified directory */
116 tree_entry *
117 tree_store_whereis (char *name)
119 tree_entry *current = ts.tree_first;
120 int flag = -1;
122 while (current && (flag = pathcmp (current->name, name)) < 0)
123 current = current->next;
125 if (flag == 0)
126 return current;
127 else
128 return NULL;
131 TreeStore *
132 tree_store_get (void)
134 return &ts;
137 static char *
138 decode (char *buffer)
140 char *res = g_strdup (buffer);
141 char *p, *q;
143 for (p = q = res; *p; p++, q++){
144 if (*p == '\n'){
145 *q = 0;
146 return res;
149 if (*p != '\\'){
150 *q = *p;
151 continue;
154 p++;
156 switch (*p){
157 case 'n':
158 *q = '\n';
159 break;
160 case '\\':
161 *q = '\\';
162 break;
165 *q = *p;
167 return res;
170 /* Loads the tree store from the specified filename */
171 static int
172 tree_store_load_from (char *name)
174 FILE *file;
175 char buffer [MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
176 char *different;
177 int len, common;
178 int do_load;
180 g_return_val_if_fail (name != NULL, FALSE);
182 if (ts.loaded)
183 return TRUE;
185 file = fopen (name, "r");
187 if (file){
188 fgets (buffer, sizeof (buffer), file);
190 if (strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0){
191 fclose (file);
192 do_load = FALSE;
193 } else
194 do_load = TRUE;
195 } else
196 do_load = FALSE;
198 if (do_load){
199 ts.loaded = TRUE;
201 /* File open -> read contents */
202 oldname [0] = 0;
203 while (fgets (buffer, MC_MAXPATHLEN, file)){
204 tree_entry *e;
205 int scanned;
206 char *name;
208 /* Skip invalid records */
209 if ((buffer [0] != '0' && buffer [0] != '1'))
210 continue;
212 if (buffer [1] != ':')
213 continue;
215 scanned = buffer [0] == '1';
217 name = decode (buffer+2);
219 len = strlen (name);
220 #ifdef OS2_NT
221 /* .ado: Drives for NT and OS/2 */
222 if ((len > 2) &&
223 isalpha(name[0]) &&
224 (name[1] == ':') &&
225 (name[2] == '\\')) {
226 tree_store_add_entry (name);
227 strcpy (oldname, name);
228 } else
229 #endif
230 /* UNIX Version */
231 if (name [0] != PATH_SEP){
232 /* Clear-text decompression */
233 char *s = strtok (name, " ");
235 if (s){
236 common = atoi (s);
237 different = strtok (NULL, "");
238 if (different){
239 strcpy (oldname + common, different);
240 if (vfs_file_is_local (oldname)){
241 e = tree_store_add_entry (oldname);
242 e->scanned = scanned;
246 } else {
247 if (vfs_file_is_local (name)){
248 e = tree_store_add_entry (name);
249 e->scanned = scanned;
251 strcpy (oldname, name);
253 g_free (name);
255 fclose (file);
258 /* Nothing loaded, we add some standard directories */
259 if (!ts.tree_first){
260 tree_store_add_entry (PATH_SEP_STR);
261 tree_store_rescan (PATH_SEP_STR);
262 ts.loaded = TRUE;
265 return TRUE;
269 * tree_store_load:
270 * @void:
272 * Loads the tree from the default location.
274 * Return value: TRUE if success, FALSE otherwise.
277 tree_store_load (void)
279 char *name;
280 int retval;
282 name = concat_dir_and_file (home_dir, MC_TREE);
283 retval = tree_store_load_from (name);
284 g_free (name);
286 return retval;
289 static char *
290 encode (char *string)
292 int special_chars;
293 char *p, *q;
294 char *res;
296 for (special_chars = 0, p = string; *p; p++){
297 if (*p == '\n' || *p == '\\')
298 special_chars++;
301 res = g_malloc (p - string + special_chars + 1);
302 for (p = string, q = res; *p; p++, q++){
303 if (*p != '\n' && *p != '\\'){
304 *q = *p;
305 continue;
308 *q++ = '\\';
310 switch (*p){
311 case '\n':
312 *q = 'n';
313 break;
315 case '\\':
316 *q = '\\';
317 break;
320 *q = 0;
321 return res;
324 /* Saves the tree to the specified filename */
325 static int
326 tree_store_save_to (char *name)
328 tree_entry *current;
329 FILE *file;
331 file = fopen (name, "w");
332 if (!file)
333 return errno;
335 fprintf (file, "%s\n", TREE_SIGNATURE);
337 current = ts.tree_first;
338 while (current){
339 int i, common;
341 if (vfs_file_is_local (current->name)){
342 /* Clear-text compression */
343 if (current->prev
344 && (common = str_common (current->prev->name, current->name)) > 2){
345 char *encoded = encode (current->name + common);
347 i = fprintf (file, "%d:%d %s\n", current->scanned, common, encoded);
348 g_free (encoded);
349 } else {
350 char *encoded = encode (current->name);
352 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
353 g_free (encoded);
356 if (i == EOF){
357 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"), name,
358 unix_error_string (errno));
359 break;
362 current = current->next;
364 tree_store_dirty (FALSE);
365 fclose (file);
367 return 0;
371 * tree_store_save:
372 * @void:
374 * Saves the tree to the default file in an atomic fashion.
376 * Return value: 0 if success, errno on error.
379 tree_store_save (void)
381 char *tmp;
382 char *name;
383 int retval;
385 tmp = concat_dir_and_file (home_dir, MC_TREE_TMP);
386 retval = tree_store_save_to (tmp);
388 if (retval) {
389 g_free (tmp);
390 return retval;
393 name = concat_dir_and_file (home_dir, MC_TREE);
394 retval = rename (tmp, name);
396 g_free (tmp);
397 g_free (name);
399 if (retval)
400 return errno;
401 else
402 return 0;
405 tree_entry *
406 tree_store_add_entry (char *name)
408 int flag = -1;
409 tree_entry *current = ts.tree_first;
410 tree_entry *old = NULL;
411 tree_entry *new;
412 int i, len;
413 int submask = 0;
415 if (ts.tree_last && ts.tree_last->next)
416 abort ();
418 /* Search for the correct place */
419 while (current && (flag = pathcmp (current->name, name)) < 0){
420 old = current;
421 current = current->next;
424 if (flag == 0)
425 return current; /* Already in the list */
427 /* Not in the list -> add it */
428 new = g_new0 (tree_entry, 1);
429 if (!current){
430 /* Append to the end of the list */
431 if (!ts.tree_first){
432 /* Empty list */
433 ts.tree_first = new;
434 new->prev = NULL;
435 } else {
436 old->next = new;
437 new->prev = old;
439 new->next = NULL;
440 ts.tree_last = new;
441 } else {
442 /* Insert in to the middle of the list */
443 new->prev = old;
444 if (old){
445 /* Yes, in the middle */
446 new->next = old->next;
447 old->next = new;
448 } else {
449 /* Nope, in the beginning of the list */
450 new->next = ts.tree_first;
451 ts.tree_first = new;
453 new->next->prev = new;
456 /* Calculate attributes */
457 new->name = g_strdup (name);
458 len = strlen (new->name);
459 new->sublevel = 0;
460 for (i = 0; i < len; i++)
461 if (new->name [i] == PATH_SEP){
462 new->sublevel++;
463 new->subname = new->name + i + 1;
465 if (new->next)
466 submask = new->next->submask;
467 else
468 submask = 0;
469 submask |= 1 << new->sublevel;
470 submask &= (2 << new->sublevel) - 1;
471 new->submask = submask;
472 new->mark = 0;
474 /* Correct the submasks of the previous entries */
475 current = new->prev;
476 while (current && current->sublevel > new->sublevel){
477 current->submask |= 1 << new->sublevel;
478 current = current->prev;
481 /* The entry has now been added */
483 if (new->sublevel > 1){
484 /* Let's check if the parent directory is in the tree */
485 char *parent = g_strdup (new->name);
486 int i;
488 for (i = strlen (parent) - 1; i > 1; i--){
489 if (parent [i] == PATH_SEP){
490 parent [i] = 0;
491 tree_store_add_entry (parent);
492 break;
495 g_free (parent);
498 tree_store_dirty (TRUE);
499 return new;
502 static tree_entry *
503 remove_entry (tree_entry *entry)
505 tree_entry *current = entry->prev;
506 long submask = 0;
507 tree_entry *ret = NULL;
509 tree_store_notify_remove (entry);
511 /* Correct the submasks of the previous entries */
512 if (entry->next)
513 submask = entry->next->submask;
514 while (current && current->sublevel > entry->sublevel){
515 submask |= 1 << current->sublevel;
516 submask &= (2 << current->sublevel) - 1;
517 current->submask = submask;
518 current = current->prev;
521 /* Unlink the entry from the list */
522 if (entry->prev)
523 entry->prev->next = entry->next;
524 else
525 ts.tree_first = entry->next;
527 if (entry->next)
528 entry->next->prev = entry->prev;
529 else
530 ts.tree_last = entry->prev;
532 /* Free the memory used by the entry */
533 g_free (entry->name);
534 g_free (entry);
536 return ret;
539 void
540 tree_store_remove_entry (char *name)
542 tree_entry *current, *base, *old;
543 int len, base_sublevel;
545 g_return_if_fail (name != NULL);
546 g_return_if_fail (ts.check_name != NULL);
548 /* Miguel Ugly hack */
549 if (name [0] == PATH_SEP && name [1] == 0)
550 return;
551 /* Miguel Ugly hack end */
553 base = tree_store_whereis (name);
554 if (!base)
555 return; /* Doesn't exist */
557 if (ts.check_name [0] == PATH_SEP && ts.check_name [1] == 0)
558 base_sublevel = base->sublevel;
559 else
560 base_sublevel = base->sublevel + 1;
562 len = strlen (base->name);
563 current = base->next;
564 while (current
565 && strncmp (current->name, base->name, len) == 0
566 && (current->name[len] == '\0' || current->name[len] == PATH_SEP)) {
567 old = current;
568 current = current->next;
569 remove_entry (old);
571 remove_entry (base);
572 tree_store_dirty (TRUE);
574 return;
577 /* This subdirectory exists -> clear deletion mark */
578 void
579 tree_store_mark_checked (const char *subname)
581 char *name;
582 tree_entry *current, *base;
583 int flag = 1, len;
584 if (!ts.loaded)
585 return;
587 if (ts.check_name == NULL)
588 return;
590 /* Calculate the full name of the subdirectory */
591 if (subname [0] == '.' &&
592 (subname [1] == 0 || (subname [1] == '.' && subname [2] == 0)))
593 return;
594 if (ts.check_name [0] == PATH_SEP && ts.check_name [1] == 0)
595 name = g_strconcat (PATH_SEP_STR, subname, NULL);
596 else
597 name = concat_dir_and_file (ts.check_name, subname);
599 /* Search for the subdirectory */
600 current = ts.check_start;
601 while (current && (flag = pathcmp (current->name, name)) < 0)
602 current = current->next;
604 if (flag != 0){
605 /* Doesn't exist -> add it */
606 current = tree_store_add_entry (name);
607 ts.add_queue = g_list_prepend (ts.add_queue, g_strdup (name));
609 g_free (name);
611 /* Clear the deletion mark from the subdirectory and its children */
612 base = current;
613 if (base){
614 len = strlen (base->name);
615 base->mark = 0;
616 current = base->next;
617 while (current
618 && strncmp (current->name, base->name, len) == 0
619 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1)){
620 current->mark = 0;
621 current = current->next;
626 /* Mark the subdirectories of the current directory for delete */
627 tree_entry *
628 tree_store_start_check (char *path)
630 tree_entry *current, *retval;
631 int len;
633 if (!ts.loaded)
634 return NULL;
636 g_return_val_if_fail (ts.check_name == NULL, NULL);
637 ts.check_start = NULL;
639 tree_store_set_freeze (TRUE);
641 /* Search for the start of subdirectories */
642 current = tree_store_whereis (path);
643 if (!current){
644 struct stat s;
646 if (mc_stat (path, &s) == -1)
647 return NULL;
649 if (!S_ISDIR (s.st_mode))
650 return NULL;
652 current = tree_store_add_entry (path);
653 ts.check_name = g_strdup (path);
655 return current;
658 ts.check_name = g_strdup (path);
660 retval = current;
662 /* Mark old subdirectories for delete */
663 ts.check_start = current->next;
664 len = strlen (ts.check_name);
666 current = ts.check_start;
667 while (current
668 && strncmp (current->name, ts.check_name, len) == 0
669 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1)){
670 current->mark = 1;
671 current = current->next;
674 return retval;
677 tree_entry *
678 tree_store_start_check_cwd (void)
680 char buffer [MC_MAXPATHLEN];
682 mc_get_current_wd (buffer, MC_MAXPATHLEN);
683 return tree_store_start_check (buffer);
686 /* Delete subdirectories which still have the deletion mark */
687 void
688 tree_store_end_check (void)
690 tree_entry *current, *old;
691 int len;
692 GList *the_queue, *l;
694 if (!ts.loaded)
695 return;
697 g_return_if_fail (ts.check_name != NULL);
699 /* Check delete marks and delete if found */
700 len = strlen (ts.check_name);
702 current = ts.check_start;
703 while (current
704 && strncmp (current->name, ts.check_name, len) == 0
705 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1)){
706 old = current;
707 current = current->next;
708 if (old->mark)
709 remove_entry (old);
712 /* get the stuff in the scan order */
713 ts.add_queue = g_list_reverse (ts.add_queue);
714 the_queue = ts.add_queue;
715 ts.add_queue = NULL;
716 g_free (ts.check_name);
717 ts.check_name = NULL;
719 for (l = the_queue; l; l = l->next){
720 tree_store_notify_add (l->data);
721 g_free (l->data);
724 g_list_free (the_queue);
726 tree_store_set_freeze (FALSE);
729 static void
730 process_special_dirs (GList **special_dirs, char *file)
732 char *token;
733 char *buffer = g_malloc (4096);
734 char *s;
736 GetPrivateProfileString ("Special dirs", "list",
737 "", buffer, 4096, file);
738 s = buffer;
739 while ((token = strtok (s, ",")) != NULL){
740 *special_dirs = g_list_prepend (*special_dirs, g_strdup (token));
741 s = NULL;
743 g_free (buffer);
746 static gboolean
747 should_skip_directory (char *dir)
749 static GList *special_dirs;
750 GList *l;
751 static int loaded;
753 if (loaded == 0){
754 loaded = 1;
755 setup_init ();
756 process_special_dirs (&special_dirs, profile_name);
757 process_special_dirs (&special_dirs, CONFDIR "mc.global");
760 for (l = special_dirs; l; l = l->next){
761 if (strncmp (dir, l->data, strlen (l->data)) == 0)
762 return TRUE;
764 return FALSE;
767 tree_entry *
768 tree_store_rescan (char *dir)
770 DIR *dirp;
771 struct dirent *dp;
772 struct stat buf;
773 tree_entry *entry;
775 if (should_skip_directory (dir)){
776 entry = tree_store_add_entry (dir);
777 entry->scanned = 1;
779 return entry;
782 entry = tree_store_start_check (dir);
784 if (!entry)
785 return NULL;
787 dirp = mc_opendir (dir);
788 if (dirp){
789 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp)){
790 char *full_name;
792 if (dp->d_name [0] == '.'){
793 if (dp->d_name [1] == 0
794 || (dp->d_name [1] == '.' && dp->d_name [2] == 0))
795 continue;
798 full_name = concat_dir_and_file (dir, dp->d_name);
799 if (mc_lstat (full_name, &buf) != -1){
800 if (S_ISDIR (buf.st_mode))
801 tree_store_mark_checked (dp->d_name);
803 g_free (full_name);
805 mc_closedir (dirp);
807 tree_store_end_check ();
808 entry->scanned = 1;
810 return entry;
813 static Hook *remove_entry_hooks;
814 static Hook *add_entry_hooks;
815 static Hook *freeze_hooks;
817 void
818 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
820 add_hook (&remove_entry_hooks, (void (*)(void *))callback, data);
823 void
824 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
826 delete_hook (&remove_entry_hooks, (void (*)(void *))callback);
829 void
830 tree_store_notify_remove (tree_entry *entry)
832 Hook *p = remove_entry_hooks;
833 tree_store_remove_fn r;
836 while (p){
837 r = (tree_store_remove_fn) p->hook_fn;
838 r (entry, p->hook_data);
839 p = p->next;
842 void
843 tree_store_add_entry_add_hook (tree_store_add_fn callback, void *data)
845 add_hook (&add_entry_hooks, (void (*)(void *))callback, data);
848 void
849 tree_store_remove_entry_add_hook (tree_store_add_fn callback)
851 delete_hook (&add_entry_hooks, (void (*)(void *))callback);
854 void
855 tree_store_notify_add (char *directory)
857 Hook *p = add_entry_hooks;
858 tree_store_add_fn r;
860 while (p) {
861 r = (tree_store_add_fn) p->hook_fn;
862 r (directory, p->hook_data);
863 p = p->next;
867 void
868 tree_store_add_freeze_hook (tree_freeze_fn callback, void *data)
870 add_hook (&freeze_hooks, (void (*)(void *)) callback, data);
873 void
874 tree_store_remove_freeze_hook (tree_freeze_fn callback)
876 delete_hook (&freeze_hooks, (void (*)(void *))callback);
879 void
880 tree_store_set_freeze (int freeze)
882 Hook *p = freeze_hooks;
883 tree_freeze_fn f;
885 while (p) {
886 f = (tree_freeze_fn) p->hook_fn;
887 f (freeze, p->hook_data);
888 p = p->next;
892 tree_scan *
893 tree_store_opendir (char *path)
895 tree_entry *entry;
896 tree_scan *scan;
898 entry = tree_store_whereis (path);
899 if (!entry || (entry && !entry->scanned)) {
900 entry = tree_store_rescan (path);
902 if (!entry)
903 return NULL;
906 if (entry->next == NULL)
907 return NULL;
909 scan = g_new (tree_scan, 1);
910 scan->base = entry;
911 scan->current = entry->next;
912 scan->sublevel = entry->next->sublevel;
914 scan->base_dir_len = strlen (path);
915 return scan;
918 tree_entry *
919 tree_store_readdir (tree_scan *scan)
921 tree_entry *entry;
922 int len;
924 g_assert (scan != NULL);
926 len = scan->base_dir_len;
927 entry = scan->current;
928 while (entry &&
929 (strncmp (entry->name, scan->base->name, len) == 0) &&
930 (entry->name [len] == 0 || entry->name [len] == PATH_SEP || len == 1)){
932 if (entry->sublevel == scan->sublevel){
933 scan->current = entry->next;
934 return entry;
936 entry = entry->next;
939 return NULL;
942 void
943 tree_store_closedir (tree_scan *scanner)
945 g_assert (scanner != NULL);
947 g_free (scanner);