Just a little correction at the it.po file.
[midnight-commander.git] / src / treestore.c
blobf3c55dd892524824c8148f9cb188820de1530156
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 <sys/stat.h>
39 #include <errno.h>
40 #include <stdio.h>
41 #include <string.h>
43 #include "global.h"
44 #include "treestore.h"
45 #include "../vfs/vfs.h"
46 #include "profile.h"
47 #include "setup.h"
49 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
51 static TreeStore ts;
53 static void
54 tree_store_dirty(int state)
56 ts.dirty = state;
59 /* Returns number of common characters */
60 static int
61 str_common(char *s1, char *s2)
63 int result = 0;
65 while (*s1++ == *s2++)
66 result++;
67 return result;
70 /* The directory names are arranged in a single linked list in the same
71 order as they are displayed. When the tree is displayed the expected
72 order is like this:
74 /bin
75 /etc
76 /etc/X11
77 /etc/rc.d
78 /etc.old/X11
79 /etc.old/rc.d
80 /usr
82 i.e. the required collating sequence when comparing two directory names is
83 '\0' < PATH_SEP < all-other-characters-in-encoding-order
85 Since strcmp doesn't fulfil this requirement we use pathcmp when
86 inserting directory names into the list. The meaning of the return value
87 of pathcmp and strcmp are the same (an integer less than, equal to, or
88 greater than zero if p1 is found to be less than, to match, or be greater
89 than p2.
91 static int
92 pathcmp(const char *p1, const char *p2)
94 for (; *p1 == *p2; p1++, p2++)
95 if (*p1 == '\0')
96 return 0;
98 if (*p1 == '\0')
99 return -1;
100 if (*p2 == '\0')
101 return 1;
102 if (*p1 == PATH_SEP)
103 return -1;
104 if (*p2 == PATH_SEP)
105 return 1;
106 return (*p1 - *p2);
109 /* Searches for specified directory */
110 tree_entry *
111 tree_store_whereis(char *name)
113 tree_entry *current = ts.tree_first;
114 int flag = -1;
116 while (current && (flag = pathcmp(current->name, name)) < 0)
117 current = current->next;
119 if (flag == 0)
120 return current;
121 else
122 return NULL;
125 TreeStore *
126 tree_store_get(void)
128 return &ts;
131 static char *
132 decode(char *buffer)
134 char *res = g_strdup(buffer);
135 char *p, *q;
137 for (p = q = res; *p; p++, q++) {
138 if (*p == '\n') {
139 *q = 0;
140 return res;
143 if (*p != '\\') {
144 *q = *p;
145 continue;
148 p++;
150 switch (*p) {
151 case 'n':
152 *q = '\n';
153 break;
154 case '\\':
155 *q = '\\';
156 break;
159 *q = *p;
161 return res;
164 /* Loads the tree store from the specified filename */
165 static int
166 tree_store_load_from(char *name)
168 FILE *file;
169 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
170 char *different;
171 int len, common;
172 int do_load;
174 g_return_val_if_fail(name != NULL, FALSE);
176 if (ts.loaded)
177 return TRUE;
179 file = fopen(name, "r");
181 if (file) {
182 fgets(buffer, sizeof(buffer), file);
184 if (strncmp(buffer, TREE_SIGNATURE, strlen(TREE_SIGNATURE)) != 0) {
185 fclose(file);
186 do_load = FALSE;
187 } else
188 do_load = TRUE;
189 } else
190 do_load = FALSE;
192 if (do_load) {
193 ts.loaded = TRUE;
195 /* File open -> read contents */
196 oldname[0] = 0;
197 while (fgets(buffer, MC_MAXPATHLEN, file)) {
198 tree_entry *e;
199 int scanned;
200 char *name;
202 /* Skip invalid records */
203 if ((buffer[0] != '0' && buffer[0] != '1'))
204 continue;
206 if (buffer[1] != ':')
207 continue;
209 scanned = buffer[0] == '1';
211 name = decode(buffer + 2);
213 len = strlen(name);
214 #ifdef NATIVE_WIN32
215 /* .ado: Drives for Win32 */
216 if ((len > 2) &&
217 isalpha(name[0]) &&
218 (name[1] == ':') && (name[2] == '\\')) {
219 tree_store_add_entry(name);
220 strcpy(oldname, name);
221 } else
222 #endif
223 /* UNIX Version */
224 if (name[0] != PATH_SEP) {
225 /* Clear-text decompression */
226 char *s = strtok(name, " ");
228 if (s) {
229 common = atoi(s);
230 different = strtok(NULL, "");
231 if (different) {
232 strcpy(oldname + common, different);
233 if (vfs_file_is_local(oldname)) {
234 e = tree_store_add_entry(oldname);
235 e->scanned = scanned;
239 } else {
240 if (vfs_file_is_local(name)) {
241 e = tree_store_add_entry(name);
242 e->scanned = scanned;
244 strcpy(oldname, name);
246 g_free(name);
248 fclose(file);
251 /* Nothing loaded, we add some standard directories */
252 if (!ts.tree_first) {
253 tree_store_add_entry(PATH_SEP_STR);
254 tree_store_rescan(PATH_SEP_STR);
255 ts.loaded = TRUE;
258 return TRUE;
262 * tree_store_load:
263 * @void:
265 * Loads the tree from the default location.
267 * Return value: TRUE if success, FALSE otherwise.
270 tree_store_load(void)
272 char *name;
273 int retval;
275 name = concat_dir_and_file(home_dir, MC_TREE);
276 retval = tree_store_load_from(name);
277 g_free(name);
279 return retval;
282 static char *
283 encode(char *string)
285 int special_chars;
286 char *p, *q;
287 char *res;
289 for (special_chars = 0, p = string; *p; p++) {
290 if (*p == '\n' || *p == '\\')
291 special_chars++;
294 res = g_malloc(p - string + special_chars + 1);
295 for (p = string, q = res; *p; p++, q++) {
296 if (*p != '\n' && *p != '\\') {
297 *q = *p;
298 continue;
301 *q++ = '\\';
303 switch (*p) {
304 case '\n':
305 *q = 'n';
306 break;
308 case '\\':
309 *q = '\\';
310 break;
313 *q = 0;
314 return res;
317 /* Saves the tree to the specified filename */
318 static int
319 tree_store_save_to(char *name)
321 tree_entry *current;
322 FILE *file;
324 file = fopen(name, "w");
325 if (!file)
326 return errno;
328 fprintf(file, "%s\n", TREE_SIGNATURE);
330 current = ts.tree_first;
331 while (current) {
332 int i, common;
334 if (vfs_file_is_local(current->name)) {
335 /* Clear-text compression */
336 if (current->prev
337 && (common =
338 str_common(current->prev->name, current->name)) > 2) {
339 char *encoded = encode(current->name + common);
341 i = fprintf(file, "%d:%d %s\n", current->scanned, common,
342 encoded);
343 g_free(encoded);
344 } else {
345 char *encoded = encode(current->name);
347 i = fprintf(file, "%d:%s\n", current->scanned, encoded);
348 g_free(encoded);
351 if (i == EOF) {
352 fprintf(stderr, _("Cannot write to the %s file:\n%s\n"),
353 name, unix_error_string(errno));
354 break;
357 current = current->next;
359 tree_store_dirty(FALSE);
360 fclose(file);
362 return 0;
366 * tree_store_save:
367 * @void:
369 * Saves the tree to the default file in an atomic fashion.
371 * Return value: 0 if success, errno on error.
374 tree_store_save(void)
376 char *tmp;
377 char *name;
378 int retval;
380 tmp = concat_dir_and_file(home_dir, MC_TREE_TMP);
381 retval = tree_store_save_to(tmp);
383 if (retval) {
384 g_free(tmp);
385 return retval;
388 name = concat_dir_and_file(home_dir, MC_TREE);
389 retval = rename(tmp, name);
391 g_free(tmp);
392 g_free(name);
394 if (retval)
395 return errno;
396 else
397 return 0;
400 tree_entry *
401 tree_store_add_entry(char *name)
403 int flag = -1;
404 tree_entry *current = ts.tree_first;
405 tree_entry *old = NULL;
406 tree_entry *new;
407 int i, len;
408 int submask = 0;
410 if (ts.tree_last && ts.tree_last->next)
411 abort();
413 /* Search for the correct place */
414 while (current && (flag = pathcmp(current->name, name)) < 0) {
415 old = current;
416 current = current->next;
419 if (flag == 0)
420 return current; /* Already in the list */
422 /* Not in the list -> add it */
423 new = g_new0(tree_entry, 1);
424 if (!current) {
425 /* Append to the end of the list */
426 if (!ts.tree_first) {
427 /* Empty list */
428 ts.tree_first = new;
429 new->prev = NULL;
430 } else {
431 old->next = new;
432 new->prev = old;
434 new->next = NULL;
435 ts.tree_last = new;
436 } else {
437 /* Insert in to the middle of the list */
438 new->prev = old;
439 if (old) {
440 /* Yes, in the middle */
441 new->next = old->next;
442 old->next = new;
443 } else {
444 /* Nope, in the beginning of the list */
445 new->next = ts.tree_first;
446 ts.tree_first = new;
448 new->next->prev = new;
451 /* Calculate attributes */
452 new->name = g_strdup(name);
453 len = strlen(new->name);
454 new->sublevel = 0;
455 for (i = 0; i < len; i++)
456 if (new->name[i] == PATH_SEP) {
457 new->sublevel++;
458 new->subname = new->name + i + 1;
460 if (new->next)
461 submask = new->next->submask;
462 else
463 submask = 0;
464 submask |= 1 << new->sublevel;
465 submask &= (2 << new->sublevel) - 1;
466 new->submask = submask;
467 new->mark = 0;
469 /* Correct the submasks of the previous entries */
470 current = new->prev;
471 while (current && current->sublevel > new->sublevel) {
472 current->submask |= 1 << new->sublevel;
473 current = current->prev;
476 /* The entry has now been added */
478 if (new->sublevel > 1) {
479 /* Let's check if the parent directory is in the tree */
480 char *parent = g_strdup(new->name);
481 int i;
483 for (i = strlen(parent) - 1; i > 1; i--) {
484 if (parent[i] == PATH_SEP) {
485 parent[i] = 0;
486 tree_store_add_entry(parent);
487 break;
490 g_free(parent);
493 tree_store_dirty(TRUE);
494 return new;
497 static Hook *remove_entry_hooks;
499 void
500 tree_store_add_entry_remove_hook(tree_store_remove_fn callback, void *data)
502 add_hook(&remove_entry_hooks, (void (*)(void *)) callback, data);
505 void
506 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback)
508 delete_hook(&remove_entry_hooks, (void (*)(void *)) callback);
511 static void
512 tree_store_notify_remove(tree_entry * entry)
514 Hook *p = remove_entry_hooks;
515 tree_store_remove_fn r;
517 while (p) {
518 r = (tree_store_remove_fn) p->hook_fn;
519 r(entry, p->hook_data);
520 p = p->next;
524 static tree_entry *
525 remove_entry(tree_entry * entry)
527 tree_entry *current = entry->prev;
528 long submask = 0;
529 tree_entry *ret = NULL;
531 tree_store_notify_remove(entry);
533 /* Correct the submasks of the previous entries */
534 if (entry->next)
535 submask = entry->next->submask;
536 while (current && current->sublevel > entry->sublevel) {
537 submask |= 1 << current->sublevel;
538 submask &= (2 << current->sublevel) - 1;
539 current->submask = submask;
540 current = current->prev;
543 /* Unlink the entry from the list */
544 if (entry->prev)
545 entry->prev->next = entry->next;
546 else
547 ts.tree_first = entry->next;
549 if (entry->next)
550 entry->next->prev = entry->prev;
551 else
552 ts.tree_last = entry->prev;
554 /* Free the memory used by the entry */
555 g_free(entry->name);
556 g_free(entry);
558 return ret;
561 void
562 tree_store_remove_entry(char *name)
564 tree_entry *current, *base, *old;
565 int len;
567 g_return_if_fail(name != NULL);
569 /* Miguel Ugly hack */
570 if (name[0] == PATH_SEP && name[1] == 0)
571 return;
572 /* Miguel Ugly hack end */
574 base = tree_store_whereis(name);
575 if (!base)
576 return; /* Doesn't exist */
578 len = strlen(base->name);
579 current = base->next;
580 while (current
581 && strncmp(current->name, base->name, len) == 0
582 && (current->name[len] == '\0'
583 || current->name[len] == PATH_SEP)) {
584 old = current;
585 current = current->next;
586 remove_entry(old);
588 remove_entry(base);
589 tree_store_dirty(TRUE);
591 return;
594 /* This subdirectory exists -> clear deletion mark */
595 void
596 tree_store_mark_checked(const char *subname)
598 char *name;
599 tree_entry *current, *base;
600 int flag = 1, len;
601 if (!ts.loaded)
602 return;
604 if (ts.check_name == NULL)
605 return;
607 /* Calculate the full name of the subdirectory */
608 if (subname[0] == '.' &&
609 (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
610 return;
611 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
612 name = g_strconcat(PATH_SEP_STR, subname, NULL);
613 else
614 name = concat_dir_and_file(ts.check_name, subname);
616 /* Search for the subdirectory */
617 current = ts.check_start;
618 while (current && (flag = pathcmp(current->name, name)) < 0)
619 current = current->next;
621 if (flag != 0) {
622 /* Doesn't exist -> add it */
623 current = tree_store_add_entry(name);
624 ts.add_queue = g_list_prepend(ts.add_queue, g_strdup(name));
626 g_free(name);
628 /* Clear the deletion mark from the subdirectory and its children */
629 base = current;
630 if (base) {
631 len = strlen(base->name);
632 base->mark = 0;
633 current = base->next;
634 while (current
635 && strncmp(current->name, base->name, len) == 0
636 && (current->name[len] == '\0'
637 || current->name[len] == PATH_SEP || len == 1)) {
638 current->mark = 0;
639 current = current->next;
644 /* Mark the subdirectories of the current directory for delete */
645 tree_entry *
646 tree_store_start_check(char *path)
648 tree_entry *current, *retval;
649 int len;
651 if (!ts.loaded)
652 return NULL;
654 g_return_val_if_fail(ts.check_name == NULL, NULL);
655 ts.check_start = NULL;
657 /* Search for the start of subdirectories */
658 current = tree_store_whereis(path);
659 if (!current) {
660 struct stat s;
662 if (mc_stat(path, &s) == -1)
663 return NULL;
665 if (!S_ISDIR(s.st_mode))
666 return NULL;
668 current = tree_store_add_entry(path);
669 ts.check_name = g_strdup(path);
671 return current;
674 ts.check_name = g_strdup(path);
676 retval = current;
678 /* Mark old subdirectories for delete */
679 ts.check_start = current->next;
680 len = strlen(ts.check_name);
682 current = ts.check_start;
683 while (current
684 && strncmp(current->name, ts.check_name, len) == 0
685 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
686 || len == 1)) {
687 current->mark = 1;
688 current = current->next;
691 return retval;
694 tree_entry *
695 tree_store_start_check_cwd(void)
697 char buffer[MC_MAXPATHLEN];
699 mc_get_current_wd(buffer, MC_MAXPATHLEN);
700 return tree_store_start_check(buffer);
703 /* Delete subdirectories which still have the deletion mark */
704 void
705 tree_store_end_check(void)
707 tree_entry *current, *old;
708 int len;
709 GList *the_queue, *l;
711 if (!ts.loaded)
712 return;
714 g_return_if_fail(ts.check_name != NULL);
716 /* Check delete marks and delete if found */
717 len = strlen(ts.check_name);
719 current = ts.check_start;
720 while (current
721 && strncmp(current->name, ts.check_name, len) == 0
722 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
723 || len == 1)) {
724 old = current;
725 current = current->next;
726 if (old->mark)
727 remove_entry(old);
730 /* get the stuff in the scan order */
731 ts.add_queue = g_list_reverse(ts.add_queue);
732 the_queue = ts.add_queue;
733 ts.add_queue = NULL;
734 g_free(ts.check_name);
735 ts.check_name = NULL;
737 for (l = the_queue; l; l = l->next) {
738 g_free(l->data);
741 g_list_free(the_queue);
744 static void
745 process_special_dirs(GList ** special_dirs, char *file)
747 char *token;
748 char *buffer = g_malloc(4096);
749 char *s;
751 GetPrivateProfileString("Special dirs", "list",
752 "", buffer, 4096, file);
753 s = buffer;
754 while ((token = strtok(s, ",")) != NULL) {
755 *special_dirs = g_list_prepend(*special_dirs, g_strdup(token));
756 s = NULL;
758 g_free(buffer);
761 static gboolean
762 should_skip_directory(char *dir)
764 static GList *special_dirs;
765 GList *l;
766 static int loaded;
768 if (loaded == 0) {
769 loaded = 1;
770 setup_init();
771 process_special_dirs(&special_dirs, profile_name);
772 process_special_dirs(&special_dirs, global_profile_name);
775 for (l = special_dirs; l; l = l->next) {
776 if (strncmp(dir, l->data, strlen(l->data)) == 0)
777 return TRUE;
779 return FALSE;
782 tree_entry *
783 tree_store_rescan(char *dir)
785 DIR *dirp;
786 struct dirent *dp;
787 struct stat buf;
788 tree_entry *entry;
790 if (should_skip_directory(dir)) {
791 entry = tree_store_add_entry(dir);
792 entry->scanned = 1;
794 return entry;
797 entry = tree_store_start_check(dir);
799 if (!entry)
800 return NULL;
802 dirp = mc_opendir(dir);
803 if (dirp) {
804 for (dp = mc_readdir(dirp); dp; dp = mc_readdir(dirp)) {
805 char *full_name;
807 if (dp->d_name[0] == '.') {
808 if (dp->d_name[1] == 0
809 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
810 continue;
813 full_name = concat_dir_and_file(dir, dp->d_name);
814 if (mc_lstat(full_name, &buf) != -1) {
815 if (S_ISDIR(buf.st_mode))
816 tree_store_mark_checked(dp->d_name);
818 g_free(full_name);
820 mc_closedir(dirp);
822 tree_store_end_check();
823 entry->scanned = 1;
825 return entry;