Codepage messages related translated & other stuff...
[midnight-commander.git] / src / treestore.c
blob53f70a3f957efbc80316aa9ec97188f57c10b848
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/param.h>
40 #include <sys/stat.h>
41 #include <errno.h>
42 #include <dirent.h>
43 #include <stdio.h>
44 #include <string.h>
45 #include "global.h"
46 #include "treestore.h"
47 #include "../vfs/vfs.h"
48 #ifdef NEEDS_IO_H
49 # include <io.h>
50 #endif
51 #include "profile.h"
52 #include "setup.h"
54 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
56 static TreeStore ts;
58 void (*tree_store_dirty_notify)(int state) = NULL;
60 void
61 tree_store_dirty (int state)
63 ts.dirty = state;
65 if (tree_store_dirty_notify)
66 (*tree_store_dirty_notify)(state);
69 /* Returns number of common characters */
70 static int str_common (char *s1, char *s2)
72 int result = 0;
74 while (*s1++ == *s2++)
75 result++;
76 return result;
79 /* The directory names are arranged in a single linked list in the same
80 order as they are displayed. When the tree is displayed the expected
81 order is like this:
83 /bin
84 /etc
85 /etc/X11
86 /etc/rc.d
87 /etc.old/X11
88 /etc.old/rc.d
89 /usr
91 i.e. the required collating sequence when comparing two directory names is
92 '\0' < PATH_SEP < all-other-characters-in-encoding-order
94 Since strcmp doesn't fulfil this requirement we use pathcmp when
95 inserting directory names into the list. The meaning of the return value
96 of pathcmp and strcmp are the same (an integer less than, equal to, or
97 greater than zero if p1 is found to be less than, to match, or be greater
98 than p2.
100 static int
101 pathcmp (const char *p1, const char *p2)
103 for ( ;*p1 == *p2; p1++, p2++)
104 if (*p1 == '\0' )
105 return 0;
107 if (*p1 == '\0')
108 return -1;
109 if (*p2 == '\0')
110 return 1;
111 if (*p1 == PATH_SEP)
112 return -1;
113 if (*p2 == PATH_SEP)
114 return 1;
115 return (*p1 - *p2);
118 /* Searches for specified directory */
119 tree_entry *
120 tree_store_whereis (char *name)
122 tree_entry *current = ts.tree_first;
123 int flag = -1;
125 while (current && (flag = pathcmp (current->name, name)) < 0)
126 current = current->next;
128 if (flag == 0)
129 return current;
130 else
131 return NULL;
134 TreeStore *
135 tree_store_get (void)
137 return &ts;
140 static char *
141 decode (char *buffer)
143 char *res = g_strdup (buffer);
144 char *p, *q;
146 for (p = q = res; *p; p++, q++){
147 if (*p == '\n'){
148 *q = 0;
149 return res;
152 if (*p != '\\'){
153 *q = *p;
154 continue;
157 p++;
159 switch (*p){
160 case 'n':
161 *q = '\n';
162 break;
163 case '\\':
164 *q = '\\';
165 break;
168 *q = *p;
170 return res;
173 /* Loads the tree store from the specified filename */
174 static int
175 tree_store_load_from (char *name)
177 FILE *file;
178 char buffer [MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
179 char *different;
180 int len, common;
181 int do_load;
183 g_return_val_if_fail (name != NULL, FALSE);
185 if (ts.loaded)
186 return TRUE;
188 file = fopen (name, "r");
190 if (file){
191 fgets (buffer, sizeof (buffer), file);
193 if (strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0){
194 fclose (file);
195 do_load = FALSE;
196 } else
197 do_load = TRUE;
198 } else
199 do_load = FALSE;
201 if (do_load){
202 ts.loaded = TRUE;
204 /* File open -> read contents */
205 oldname [0] = 0;
206 while (fgets (buffer, MC_MAXPATHLEN, file)){
207 tree_entry *e;
208 int scanned;
209 char *name;
211 /* Skip invalid records */
212 if ((buffer [0] != '0' && buffer [0] != '1'))
213 continue;
215 if (buffer [1] != ':')
216 continue;
218 scanned = buffer [0] == '1';
220 name = decode (buffer+2);
222 len = strlen (name);
223 #ifdef OS2_NT
224 /* .ado: Drives for NT and OS/2 */
225 if ((len > 2) &&
226 isalpha(name[0]) &&
227 (name[1] == ':') &&
228 (name[2] == '\\')) {
229 tree_store_add_entry (name);
230 strcpy (oldname, name);
231 } else
232 #endif
233 /* UNIX Version */
234 if (name [0] != PATH_SEP){
235 /* Clear-text decompression */
236 char *s = strtok (name, " ");
238 if (s){
239 common = atoi (s);
240 different = strtok (NULL, "");
241 if (different){
242 strcpy (oldname + common, different);
243 if (vfs_file_is_local (oldname)){
244 e = tree_store_add_entry (oldname);
245 e->scanned = scanned;
249 } else {
250 if (vfs_file_is_local (name)){
251 e = tree_store_add_entry (name);
252 e->scanned = scanned;
254 strcpy (oldname, name);
256 g_free (name);
258 fclose (file);
261 /* Nothing loaded, we add some standard directories */
262 if (!ts.tree_first){
263 tree_store_add_entry (PATH_SEP_STR);
264 tree_store_rescan (PATH_SEP_STR);
265 ts.loaded = TRUE;
268 return TRUE;
272 * tree_store_load:
273 * @void:
275 * Loads the tree from the default location.
277 * Return value: TRUE if success, FALSE otherwise.
280 tree_store_load (void)
282 char *name;
283 int retval;
285 name = concat_dir_and_file (home_dir, MC_TREE);
286 retval = tree_store_load_from (name);
287 g_free (name);
289 return retval;
292 static char *
293 encode (char *string)
295 int special_chars;
296 char *p, *q;
297 char *res;
299 for (special_chars = 0, p = string; *p; p++){
300 if (*p == '\n' || *p == '\\')
301 special_chars++;
304 res = g_malloc (p - string + special_chars + 1);
305 for (p = string, q = res; *p; p++, q++){
306 if (*p != '\n' && *p != '\\'){
307 *q = *p;
308 continue;
311 *q++ = '\\';
313 switch (*p){
314 case '\n':
315 *q = 'n';
316 break;
318 case '\\':
319 *q = '\\';
320 break;
323 *q = 0;
324 return res;
327 /* Saves the tree to the specified filename */
328 static int
329 tree_store_save_to (char *name)
331 tree_entry *current;
332 FILE *file;
334 file = fopen (name, "w");
335 if (!file)
336 return errno;
338 fprintf (file, "%s\n", TREE_SIGNATURE);
340 current = ts.tree_first;
341 while (current){
342 int i, common;
344 if (vfs_file_is_local (current->name)){
345 /* Clear-text compression */
346 if (current->prev
347 && (common = str_common (current->prev->name, current->name)) > 2){
348 char *encoded = encode (current->name + common);
350 i = fprintf (file, "%d:%d %s\n", current->scanned, common, encoded);
351 g_free (encoded);
352 } else {
353 char *encoded = encode (current->name);
355 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
356 g_free (encoded);
359 if (i == EOF){
360 fprintf (stderr, _("Can't write to the %s file:\n%s\n"), name,
361 unix_error_string (errno));
362 break;
365 current = current->next;
367 tree_store_dirty (FALSE);
368 fclose (file);
370 return 0;
374 * tree_store_save:
375 * @void:
377 * Saves the tree to the default file in an atomic fashion.
379 * Return value: 0 if success, errno on error.
382 tree_store_save (void)
384 char *tmp;
385 char *name;
386 int retval;
388 tmp = concat_dir_and_file (home_dir, MC_TREE_TMP);
389 retval = tree_store_save_to (tmp);
391 if (retval) {
392 g_free (tmp);
393 return retval;
396 name = concat_dir_and_file (home_dir, MC_TREE);
397 retval = rename (tmp, name);
399 g_free (tmp);
400 g_free (name);
402 if (retval)
403 return errno;
404 else
405 return 0;
408 tree_entry *
409 tree_store_add_entry (char *name)
411 int flag = -1;
412 tree_entry *current = ts.tree_first;
413 tree_entry *old = NULL;
414 tree_entry *new;
415 int i, len;
416 int submask = 0;
418 if (ts.tree_last && ts.tree_last->next)
419 abort ();
421 /* Search for the correct place */
422 while (current && (flag = pathcmp (current->name, name)) < 0){
423 old = current;
424 current = current->next;
427 if (flag == 0)
428 return current; /* Already in the list */
430 /* Not in the list -> add it */
431 new = g_new0 (tree_entry, 1);
432 if (!current){
433 /* Append to the end of the list */
434 if (!ts.tree_first){
435 /* Empty list */
436 ts.tree_first = new;
437 new->prev = NULL;
438 } else {
439 old->next = new;
440 new->prev = old;
442 new->next = NULL;
443 ts.tree_last = new;
444 } else {
445 /* Insert in to the middle of the list */
446 new->prev = old;
447 if (old){
448 /* Yes, in the middle */
449 new->next = old->next;
450 old->next = new;
451 } else {
452 /* Nope, in the beginning of the list */
453 new->next = ts.tree_first;
454 ts.tree_first = new;
456 new->next->prev = new;
459 /* Calculate attributes */
460 new->name = g_strdup (name);
461 len = strlen (new->name);
462 new->sublevel = 0;
463 for (i = 0; i < len; i++)
464 if (new->name [i] == PATH_SEP){
465 new->sublevel++;
466 new->subname = new->name + i + 1;
468 if (new->next)
469 submask = new->next->submask;
470 else
471 submask = 0;
472 submask |= 1 << new->sublevel;
473 submask &= (2 << new->sublevel) - 1;
474 new->submask = submask;
475 new->mark = 0;
477 /* Correct the submasks of the previous entries */
478 current = new->prev;
479 while (current && current->sublevel > new->sublevel){
480 current->submask |= 1 << new->sublevel;
481 current = current->prev;
484 /* The entry has now been added */
486 if (new->sublevel > 1){
487 /* Let's check if the parent directory is in the tree */
488 char *parent = g_strdup (new->name);
489 int i;
491 for (i = strlen (parent) - 1; i > 1; i--){
492 if (parent [i] == PATH_SEP){
493 parent [i] = 0;
494 tree_store_add_entry (parent);
495 break;
498 g_free (parent);
501 tree_store_dirty (TRUE);
502 return new;
505 tree_entry *
506 remove_entry (tree_entry *entry)
508 tree_entry *current = entry->prev;
509 long submask = 0;
510 tree_entry *ret = NULL;
512 tree_store_notify_remove (entry);
514 /* Correct the submasks of the previous entries */
515 if (entry->next)
516 submask = entry->next->submask;
517 while (current && current->sublevel > entry->sublevel){
518 submask |= 1 << current->sublevel;
519 submask &= (2 << current->sublevel) - 1;
520 current->submask = submask;
521 current = current->prev;
524 /* Unlink the entry from the list */
525 if (entry->prev)
526 entry->prev->next = entry->next;
527 else
528 ts.tree_first = entry->next;
530 if (entry->next)
531 entry->next->prev = entry->prev;
532 else
533 ts.tree_last = entry->prev;
535 /* Free the memory used by the entry */
536 g_free (entry->name);
537 g_free (entry);
539 return ret;
542 void
543 tree_store_remove_entry (char *name)
545 tree_entry *current, *base, *old;
546 int len, base_sublevel;
548 g_return_if_fail (name != NULL);
549 g_return_if_fail (ts.check_name != NULL);
551 /* Miguel Ugly hack */
552 if (name [0] == PATH_SEP && name [1] == 0)
553 return;
554 /* Miguel Ugly hack end */
556 base = tree_store_whereis (name);
557 if (!base)
558 return; /* Doesn't exist */
560 if (ts.check_name [0] == PATH_SEP && ts.check_name [1] == 0)
561 base_sublevel = base->sublevel;
562 else
563 base_sublevel = base->sublevel + 1;
565 len = strlen (base->name);
566 current = base->next;
567 while (current
568 && strncmp (current->name, base->name, len) == 0
569 && (current->name[len] == '\0' || current->name[len] == PATH_SEP)) {
570 old = current;
571 current = current->next;
572 remove_entry (old);
574 remove_entry (base);
575 tree_store_dirty (TRUE);
577 return;
580 /* This subdirectory exists -> clear deletion mark */
581 void
582 tree_store_mark_checked (const char *subname)
584 char *name;
585 tree_entry *current, *base;
586 int flag = 1, len;
587 if (!ts.loaded)
588 return;
590 if (ts.check_name == NULL)
591 return;
593 /* Calculate the full name of the subdirectory */
594 if (subname [0] == '.' &&
595 (subname [1] == 0 || (subname [1] == '.' && subname [2] == 0)))
596 return;
597 if (ts.check_name [0] == PATH_SEP && ts.check_name [1] == 0)
598 name = g_strconcat (PATH_SEP_STR, subname, NULL);
599 else
600 name = concat_dir_and_file (ts.check_name, subname);
602 /* Search for the subdirectory */
603 current = ts.check_start;
604 while (current && (flag = pathcmp (current->name, name)) < 0)
605 current = current->next;
607 if (flag != 0){
608 /* Doesn't exist -> add it */
609 current = tree_store_add_entry (name);
610 ts.add_queue = g_list_prepend (ts.add_queue, g_strdup (name));
612 g_free (name);
614 /* Clear the deletion mark from the subdirectory and its children */
615 base = current;
616 if (base){
617 len = strlen (base->name);
618 base->mark = 0;
619 current = base->next;
620 while (current
621 && strncmp (current->name, base->name, len) == 0
622 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1)){
623 current->mark = 0;
624 current = current->next;
629 /* Mark the subdirectories of the current directory for delete */
630 tree_entry *
631 tree_store_start_check (char *path)
633 tree_entry *current, *retval;
634 int len;
636 if (!ts.loaded)
637 return NULL;
639 g_return_val_if_fail (ts.check_name == NULL, NULL);
640 ts.check_start = NULL;
642 tree_store_set_freeze (TRUE);
644 /* Search for the start of subdirectories */
645 current = tree_store_whereis (path);
646 if (!current){
647 struct stat s;
649 if (mc_stat (path, &s) == -1)
650 return NULL;
652 if (!S_ISDIR (s.st_mode))
653 return NULL;
655 current = tree_store_add_entry (path);
656 ts.check_name = g_strdup (path);
658 return current;
661 ts.check_name = g_strdup (path);
663 retval = current;
665 /* Mark old subdirectories for delete */
666 ts.check_start = current->next;
667 len = strlen (ts.check_name);
669 current = ts.check_start;
670 while (current
671 && strncmp (current->name, ts.check_name, len) == 0
672 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1)){
673 current->mark = 1;
674 current = current->next;
677 return retval;
680 tree_entry *
681 tree_store_start_check_cwd (void)
683 char buffer [MC_MAXPATHLEN];
685 mc_get_current_wd (buffer, MC_MAXPATHLEN);
686 return tree_store_start_check (buffer);
689 /* Delete subdirectories which still have the deletion mark */
690 void
691 tree_store_end_check (void)
693 tree_entry *current, *old;
694 int len;
695 GList *the_queue, *l;
697 if (!ts.loaded)
698 return;
700 g_return_if_fail (ts.check_name != NULL);
702 /* Check delete marks and delete if found */
703 len = strlen (ts.check_name);
705 current = ts.check_start;
706 while (current
707 && strncmp (current->name, ts.check_name, len) == 0
708 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1)){
709 old = current;
710 current = current->next;
711 if (old->mark)
712 remove_entry (old);
715 /* get the stuff in the scan order */
716 ts.add_queue = g_list_reverse (ts.add_queue);
717 the_queue = ts.add_queue;
718 ts.add_queue = NULL;
719 g_free (ts.check_name);
720 ts.check_name = NULL;
722 for (l = the_queue; l; l = l->next){
723 tree_store_notify_add (l->data);
724 g_free (l->data);
727 g_list_free (the_queue);
729 tree_store_set_freeze (FALSE);
732 static void
733 process_special_dirs (GList **special_dirs, char *file)
735 char *token;
736 char *buffer = g_malloc (4096);
737 char *s;
739 GetPrivateProfileString ("Special dirs", "list",
740 "", buffer, 4096, file);
741 s = buffer;
742 while ((token = strtok (s, ",")) != NULL){
743 *special_dirs = g_list_prepend (*special_dirs, g_strdup (token));
744 s = NULL;
746 g_free (buffer);
749 gboolean
750 should_skip_directory (char *dir)
752 static GList *special_dirs;
753 GList *l;
754 static int loaded;
756 if (loaded == 0){
757 loaded = 1;
758 setup_init ();
759 process_special_dirs (&special_dirs, profile_name);
760 process_special_dirs (&special_dirs, CONFDIR "mc.global");
763 for (l = special_dirs; l; l = l->next){
764 if (strncmp (dir, l->data, strlen (l->data)) == 0)
765 return TRUE;
767 return FALSE;
770 tree_entry *
771 tree_store_rescan (char *dir)
773 DIR *dirp;
774 struct dirent *dp;
775 struct stat buf;
776 tree_entry *entry;
778 if (should_skip_directory (dir)){
779 entry = tree_store_add_entry (dir);
780 entry->scanned = 1;
782 return entry;
785 entry = tree_store_start_check (dir);
787 if (!entry)
788 return NULL;
790 dirp = mc_opendir (dir);
791 if (dirp){
792 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp)){
793 char *full_name;
795 if (dp->d_name [0] == '.'){
796 if (dp->d_name [1] == 0
797 || (dp->d_name [1] == '.' && dp->d_name [2] == 0))
798 continue;
801 full_name = concat_dir_and_file (dir, dp->d_name);
802 if (mc_lstat (full_name, &buf) != -1){
803 if (S_ISDIR (buf.st_mode))
804 tree_store_mark_checked (dp->d_name);
806 g_free (full_name);
808 mc_closedir (dirp);
810 tree_store_end_check ();
811 entry->scanned = 1;
813 return entry;
816 static Hook *remove_entry_hooks;
817 static Hook *add_entry_hooks;
818 static Hook *freeze_hooks;
820 void
821 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
823 add_hook (&remove_entry_hooks, (void (*)(void *))callback, data);
826 void
827 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
829 delete_hook (&remove_entry_hooks, (void (*)(void *))callback);
832 void
833 tree_store_notify_remove (tree_entry *entry)
835 Hook *p = remove_entry_hooks;
836 tree_store_remove_fn r;
839 while (p){
840 r = (tree_store_remove_fn) p->hook_fn;
841 r (entry, p->hook_data);
842 p = p->next;
845 void
846 tree_store_add_entry_add_hook (tree_store_add_fn callback, void *data)
848 add_hook (&add_entry_hooks, (void (*)(void *))callback, data);
851 void
852 tree_store_remove_entry_add_hook (tree_store_add_fn callback)
854 delete_hook (&add_entry_hooks, (void (*)(void *))callback);
857 void
858 tree_store_notify_add (char *directory)
860 Hook *p = add_entry_hooks;
861 tree_store_add_fn r;
863 while (p) {
864 r = (tree_store_add_fn) p->hook_fn;
865 r (directory, p->hook_data);
866 p = p->next;
870 void
871 tree_store_add_freeze_hook (tree_freeze_fn callback, void *data)
873 add_hook (&freeze_hooks, (void (*)(void *)) callback, data);
876 void
877 tree_store_remove_freeze_hook (tree_freeze_fn callback)
879 delete_hook (&freeze_hooks, (void (*)(void *))callback);
882 void
883 tree_store_set_freeze (int freeze)
885 Hook *p = freeze_hooks;
886 tree_freeze_fn f;
888 while (p) {
889 f = (tree_freeze_fn) p->hook_fn;
890 f (freeze, p->hook_data);
891 p = p->next;
895 tree_scan *
896 tree_store_opendir (char *path)
898 tree_entry *entry;
899 tree_scan *scan;
901 entry = tree_store_whereis (path);
902 if (!entry || (entry && !entry->scanned)) {
903 entry = tree_store_rescan (path);
905 if (!entry)
906 return NULL;
909 if (entry->next == NULL)
910 return NULL;
912 scan = g_new (tree_scan, 1);
913 scan->base = entry;
914 scan->current = entry->next;
915 scan->sublevel = entry->next->sublevel;
917 scan->base_dir_len = strlen (path);
918 return scan;
921 tree_entry *
922 tree_store_readdir (tree_scan *scan)
924 tree_entry *entry;
925 int len;
927 g_assert (scan != NULL);
929 len = scan->base_dir_len;
930 entry = scan->current;
931 while (entry &&
932 (strncmp (entry->name, scan->base->name, len) == 0) &&
933 (entry->name [len] == 0 || entry->name [len] == PATH_SEP || len == 1)){
935 if (entry->sublevel == scan->sublevel){
936 scan->current = entry->next;
937 return entry;
939 entry = entry->next;
942 return NULL;
945 void
946 tree_store_closedir (tree_scan *scanner)
948 g_assert (scanner != NULL);
950 g_free (scanner);