Not only comment it out but removing it
[midnight-commander.git] / src / treestore.c
blob426b0c2894d78ff2bd4b6411d43f3b9e4eaf7591
1 /*
2 * Tree Store
4 * Contains a storage of the file system tree representation
6 Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007
7 Free Software Foundation, Inc.
9 Written: 1994, 1996 Janne Kukonlehto
10 1997 Norbert Warmuth
11 1996, 1999 Miguel de Icaza
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 2 of the License, or
16 (at your option) any later version.
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 This module has been converted to be a widget.
29 The program load and saves the tree each time the tree widget is
30 created and destroyed. This is required for the future vfs layer,
31 it will be possible to have tree views over virtual file systems.
34 #include <config.h>
36 #include <errno.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <unistd.h>
45 #include "global.h"
46 #include "treestore.h"
47 #include "profile.h"
48 #include "setup.h"
50 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
52 static struct TreeStore ts;
54 static tree_entry *tree_store_add_entry(const char *name);
56 static void
57 tree_store_dirty(int state)
59 ts.dirty = state;
62 /* Returns the number of common bytes in the strings. */
63 static size_t
64 str_common(const char *s1, const char *s2)
66 size_t result = 0;
68 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
69 result++;
70 return result;
73 /* The directory names are arranged in a single linked list in the same
74 order as they are displayed. When the tree is displayed the expected
75 order is like this:
77 /bin
78 /etc
79 /etc/X11
80 /etc/rc.d
81 /etc.old/X11
82 /etc.old/rc.d
83 /usr
85 i.e. the required collating sequence when comparing two directory names is
86 '\0' < PATH_SEP < all-other-characters-in-encoding-order
88 Since strcmp doesn't fulfil this requirement we use pathcmp when
89 inserting directory names into the list. The meaning of the return value
90 of pathcmp and strcmp are the same (an integer less than, equal to, or
91 greater than zero if p1 is found to be less than, to match, or be greater
92 than p2.
94 static int
95 pathcmp(const char *p1, const char *p2)
97 for (; *p1 == *p2; p1++, p2++)
98 if (*p1 == '\0')
99 return 0;
101 if (*p1 == '\0')
102 return -1;
103 if (*p2 == '\0')
104 return 1;
105 if (*p1 == PATH_SEP)
106 return -1;
107 if (*p2 == PATH_SEP)
108 return 1;
109 return (*p1 - *p2);
112 /* Searches for specified directory */
113 tree_entry *
114 tree_store_whereis(const char *name)
116 tree_entry *current = ts.tree_first;
117 int flag = -1;
119 while (current && (flag = pathcmp(current->name, name)) < 0)
120 current = current->next;
122 if (flag == 0)
123 return current;
124 else
125 return NULL;
128 struct TreeStore *
129 tree_store_get(void)
131 return &ts;
134 static char *
135 decode(char *buffer)
137 char *res = g_strdup(buffer);
138 char *p, *q;
140 for (p = q = res; *p; p++, q++) {
141 if (*p == '\n') {
142 *q = 0;
143 return res;
146 if (*p != '\\') {
147 *q = *p;
148 continue;
151 p++;
153 switch (*p) {
154 case 'n':
155 *q = '\n';
156 break;
157 case '\\':
158 *q = '\\';
159 break;
162 *q = *p;
164 return res;
167 /* Loads the tree store from the specified filename */
168 static int
169 tree_store_load_from(char *name)
171 FILE *file;
172 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
173 char *different;
174 int len, common;
175 int do_load;
177 g_return_val_if_fail(name != NULL, FALSE);
179 if (ts.loaded)
180 return TRUE;
182 file = fopen(name, "r");
184 if (file) {
185 fgets(buffer, sizeof(buffer), file);
187 if (strncmp(buffer, TREE_SIGNATURE, strlen(TREE_SIGNATURE)) != 0) {
188 fclose(file);
189 do_load = FALSE;
190 } else
191 do_load = TRUE;
192 } else
193 do_load = FALSE;
195 if (do_load) {
196 ts.loaded = TRUE;
198 /* File open -> read contents */
199 oldname[0] = 0;
200 while (fgets(buffer, MC_MAXPATHLEN, file)) {
201 tree_entry *e;
202 int scanned;
203 char *name;
205 /* Skip invalid records */
206 if ((buffer[0] != '0' && buffer[0] != '1'))
207 continue;
209 if (buffer[1] != ':')
210 continue;
212 scanned = buffer[0] == '1';
214 name = decode(buffer + 2);
216 len = strlen(name);
217 if (name[0] != PATH_SEP) {
218 /* Clear-text decompression */
219 char *s = strtok(name, " ");
221 if (s) {
222 common = atoi(s);
223 different = strtok(NULL, "");
224 if (different) {
225 strcpy(oldname + common, different);
226 if (vfs_file_is_local(oldname)) {
227 e = tree_store_add_entry(oldname);
228 e->scanned = scanned;
232 } else {
233 if (vfs_file_is_local(name)) {
234 e = tree_store_add_entry(name);
235 e->scanned = scanned;
237 strcpy(oldname, name);
239 g_free(name);
241 fclose(file);
244 /* Nothing loaded, we add some standard directories */
245 if (!ts.tree_first) {
246 tree_store_add_entry(PATH_SEP_STR);
247 tree_store_rescan(PATH_SEP_STR);
248 ts.loaded = TRUE;
251 return TRUE;
255 * tree_store_load:
256 * @void:
258 * Loads the tree from the default location.
260 * Return value: TRUE if success, FALSE otherwise.
263 tree_store_load(void)
265 char *name;
266 int retval;
268 name = concat_dir_and_file(home_dir, MC_TREE);
269 retval = tree_store_load_from(name);
270 g_free(name);
272 return retval;
275 static char *
276 encode(const char *string)
278 int special_chars;
279 const char *p;
280 char *q;
281 char *res;
283 for (special_chars = 0, p = string; *p; p++) {
284 if (*p == '\n' || *p == '\\')
285 special_chars++;
288 res = g_malloc(p - string + special_chars + 1);
289 for (p = string, q = res; *p; p++, q++) {
290 if (*p != '\n' && *p != '\\') {
291 *q = *p;
292 continue;
295 *q++ = '\\';
297 switch (*p) {
298 case '\n':
299 *q = 'n';
300 break;
302 case '\\':
303 *q = '\\';
304 break;
307 *q = 0;
308 return res;
311 /* Saves the tree to the specified filename */
312 static int
313 tree_store_save_to(char *name)
315 tree_entry *current;
316 FILE *file;
318 file = fopen(name, "w");
319 if (!file)
320 return errno;
322 fprintf(file, "%s\n", TREE_SIGNATURE);
324 current = ts.tree_first;
325 while (current) {
326 int i, common;
328 if (vfs_file_is_local(current->name)) {
329 /* Clear-text compression */
330 if (current->prev
331 && (common =
332 str_common(current->prev->name, current->name)) > 2) {
333 char *encoded = encode(current->name + common);
335 i = fprintf(file, "%d:%d %s\n", current->scanned, common,
336 encoded);
337 g_free(encoded);
338 } else {
339 char *encoded = encode(current->name);
341 i = fprintf(file, "%d:%s\n", current->scanned, encoded);
342 g_free(encoded);
345 if (i == EOF) {
346 fprintf(stderr, _("Cannot write to the %s file:\n%s\n"),
347 name, unix_error_string(errno));
348 break;
351 current = current->next;
353 tree_store_dirty(FALSE);
354 fclose(file);
356 return 0;
360 * tree_store_save:
361 * @void:
363 * Saves the tree to the default file in an atomic fashion.
365 * Return value: 0 if success, errno on error.
368 tree_store_save(void)
370 char *tmp;
371 char *name;
372 int retval;
374 tmp = concat_dir_and_file(home_dir, MC_TREE_TMP);
375 retval = tree_store_save_to(tmp);
377 if (retval) {
378 g_free(tmp);
379 return retval;
382 name = concat_dir_and_file(home_dir, MC_TREE);
383 retval = rename(tmp, name);
385 g_free(tmp);
386 g_free(name);
388 if (retval)
389 return errno;
390 else
391 return 0;
394 static tree_entry *
395 tree_store_add_entry(const char *name)
397 int flag = -1;
398 tree_entry *current = ts.tree_first;
399 tree_entry *old = NULL;
400 tree_entry *new;
401 int i, len;
402 int submask = 0;
404 if (ts.tree_last && ts.tree_last->next)
405 abort();
407 /* Search for the correct place */
408 while (current && (flag = pathcmp(current->name, name)) < 0) {
409 old = current;
410 current = current->next;
413 if (flag == 0)
414 return current; /* Already in the list */
416 /* Not in the list -> add it */
417 new = g_new0(tree_entry, 1);
418 if (!current) {
419 /* Append to the end of the list */
420 if (!ts.tree_first) {
421 /* Empty list */
422 ts.tree_first = new;
423 new->prev = NULL;
424 } else {
425 old->next = new;
426 new->prev = old;
428 new->next = NULL;
429 ts.tree_last = new;
430 } else {
431 /* Insert in to the middle of the list */
432 new->prev = old;
433 if (old) {
434 /* Yes, in the middle */
435 new->next = old->next;
436 old->next = new;
437 } else {
438 /* Nope, in the beginning of the list */
439 new->next = ts.tree_first;
440 ts.tree_first = new;
442 new->next->prev = new;
445 /* Calculate attributes */
446 new->name = g_strdup(name);
447 len = strlen(new->name);
448 new->sublevel = 0;
449 for (i = 0; i < len; i++)
450 if (new->name[i] == PATH_SEP) {
451 new->sublevel++;
452 new->subname = new->name + i + 1;
454 if (new->next)
455 submask = new->next->submask;
456 else
457 submask = 0;
458 submask |= 1 << new->sublevel;
459 submask &= (2 << new->sublevel) - 1;
460 new->submask = submask;
461 new->mark = 0;
463 /* Correct the submasks of the previous entries */
464 current = new->prev;
465 while (current && current->sublevel > new->sublevel) {
466 current->submask |= 1 << new->sublevel;
467 current = current->prev;
470 /* The entry has now been added */
472 if (new->sublevel > 1) {
473 /* Let's check if the parent directory is in the tree */
474 char *parent = g_strdup(new->name);
475 int i;
477 for (i = strlen(parent) - 1; i > 1; i--) {
478 if (parent[i] == PATH_SEP) {
479 parent[i] = 0;
480 tree_store_add_entry(parent);
481 break;
484 g_free(parent);
487 tree_store_dirty(TRUE);
488 return new;
491 static Hook *remove_entry_hooks;
493 void
494 tree_store_add_entry_remove_hook(tree_store_remove_fn callback, void *data)
496 add_hook(&remove_entry_hooks, (void (*)(void *)) callback, data);
499 void
500 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback)
502 delete_hook(&remove_entry_hooks, (void (*)(void *)) callback);
505 static void
506 tree_store_notify_remove(tree_entry * entry)
508 Hook *p = remove_entry_hooks;
509 tree_store_remove_fn r;
511 while (p) {
512 r = (tree_store_remove_fn) p->hook_fn;
513 r(entry, p->hook_data);
514 p = p->next;
518 static tree_entry *
519 remove_entry(tree_entry * entry)
521 tree_entry *current = entry->prev;
522 long submask = 0;
523 tree_entry *ret = NULL;
525 tree_store_notify_remove(entry);
527 /* Correct the submasks of the previous entries */
528 if (entry->next)
529 submask = entry->next->submask;
530 while (current && current->sublevel > entry->sublevel) {
531 submask |= 1 << current->sublevel;
532 submask &= (2 << current->sublevel) - 1;
533 current->submask = submask;
534 current = current->prev;
537 /* Unlink the entry from the list */
538 if (entry->prev)
539 entry->prev->next = entry->next;
540 else
541 ts.tree_first = entry->next;
543 if (entry->next)
544 entry->next->prev = entry->prev;
545 else
546 ts.tree_last = entry->prev;
548 /* Free the memory used by the entry */
549 g_free(entry->name);
550 g_free(entry);
552 return ret;
555 void
556 tree_store_remove_entry(const char *name)
558 tree_entry *current, *base, *old;
559 int len;
561 g_return_if_fail(name != NULL);
563 /* Miguel Ugly hack */
564 if (name[0] == PATH_SEP && name[1] == 0)
565 return;
566 /* Miguel Ugly hack end */
568 base = tree_store_whereis(name);
569 if (!base)
570 return; /* Doesn't exist */
572 len = strlen(base->name);
573 current = base->next;
574 while (current
575 && strncmp(current->name, base->name, len) == 0
576 && (current->name[len] == '\0'
577 || current->name[len] == PATH_SEP)) {
578 old = current;
579 current = current->next;
580 remove_entry(old);
582 remove_entry(base);
583 tree_store_dirty(TRUE);
585 return;
588 /* This subdirectory exists -> clear deletion mark */
589 void
590 tree_store_mark_checked(const char *subname)
592 char *name;
593 tree_entry *current, *base;
594 int flag = 1, len;
595 if (!ts.loaded)
596 return;
598 if (ts.check_name == NULL)
599 return;
601 /* Calculate the full name of the subdirectory */
602 if (subname[0] == '.' &&
603 (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
604 return;
605 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
606 name = g_strconcat(PATH_SEP_STR, subname, (char *) NULL);
607 else
608 name = concat_dir_and_file(ts.check_name, subname);
610 /* Search for the subdirectory */
611 current = ts.check_start;
612 while (current && (flag = pathcmp(current->name, name)) < 0)
613 current = current->next;
615 if (flag != 0) {
616 /* Doesn't exist -> add it */
617 current = tree_store_add_entry(name);
618 ts.add_queue = g_list_prepend(ts.add_queue, g_strdup(name));
620 g_free(name);
622 /* Clear the deletion mark from the subdirectory and its children */
623 base = current;
624 if (base) {
625 len = strlen(base->name);
626 base->mark = 0;
627 current = base->next;
628 while (current
629 && strncmp(current->name, base->name, len) == 0
630 && (current->name[len] == '\0'
631 || current->name[len] == PATH_SEP || len == 1)) {
632 current->mark = 0;
633 current = current->next;
638 /* Mark the subdirectories of the current directory for delete */
639 tree_entry *
640 tree_store_start_check(const char *path)
642 tree_entry *current, *retval;
643 int len;
645 if (!ts.loaded)
646 return NULL;
648 g_return_val_if_fail(ts.check_name == NULL, NULL);
649 ts.check_start = NULL;
651 /* Search for the start of subdirectories */
652 current = tree_store_whereis(path);
653 if (!current) {
654 struct stat s;
656 if (mc_stat(path, &s) == -1)
657 return NULL;
659 if (!S_ISDIR(s.st_mode))
660 return NULL;
662 current = tree_store_add_entry(path);
663 ts.check_name = g_strdup(path);
665 return current;
668 ts.check_name = g_strdup(path);
670 retval = current;
672 /* Mark old subdirectories for delete */
673 ts.check_start = current->next;
674 len = strlen(ts.check_name);
676 current = ts.check_start;
677 while (current
678 && strncmp(current->name, ts.check_name, len) == 0
679 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
680 || len == 1)) {
681 current->mark = 1;
682 current = current->next;
685 return retval;
688 /* Delete subdirectories which still have the deletion mark */
689 void
690 tree_store_end_check(void)
692 tree_entry *current, *old;
693 int len;
694 GList *the_queue, *l;
696 if (!ts.loaded)
697 return;
699 g_return_if_fail(ts.check_name != NULL);
701 /* Check delete marks and delete if found */
702 len = strlen(ts.check_name);
704 current = ts.check_start;
705 while (current
706 && strncmp(current->name, ts.check_name, len) == 0
707 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
708 || 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 g_free(l->data);
726 g_list_free(the_queue);
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(const 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, global_profile_name);
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(const 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;