Forgot to remove some more .s strings and do a rename in order to prevent compiler...
[midnight-commander.git] / src / treestore.c
blob91cfb1e3715f37617f575378748e6708a118b9ac
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>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
44 #include <mhl/types.h>
45 #include <mhl/memory.h>
46 #include <mhl/string.h>
48 #include "global.h"
49 #include "treestore.h"
50 #include "profile.h"
51 #include "setup.h"
53 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
55 static struct TreeStore ts;
57 static tree_entry *tree_store_add_entry(const char *name);
59 static void
60 tree_store_dirty(int state)
62 ts.dirty = state;
65 /* Returns the number of common bytes in the strings. */
66 static size_t
67 str_common(const char *s1, const char *s2)
69 size_t result = 0;
71 while (*s1 != '\0' && *s2 != '\0' && *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(const 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 struct 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 if (name[0] != PATH_SEP) {
221 /* Clear-text decompression */
222 char *s = strtok(name, " ");
224 if (s) {
225 common = atoi(s);
226 different = strtok(NULL, "");
227 if (different) {
228 strcpy(oldname + common, different);
229 if (vfs_file_is_local(oldname)) {
230 e = tree_store_add_entry(oldname);
231 e->scanned = scanned;
235 } else {
236 if (vfs_file_is_local(name)) {
237 e = tree_store_add_entry(name);
238 e->scanned = scanned;
240 strcpy(oldname, name);
242 g_free(name);
244 fclose(file);
247 /* Nothing loaded, we add some standard directories */
248 if (!ts.tree_first) {
249 tree_store_add_entry(PATH_SEP_STR);
250 tree_store_rescan(PATH_SEP_STR);
251 ts.loaded = TRUE;
254 return TRUE;
258 * tree_store_load:
259 * @void:
261 * Loads the tree from the default location.
263 * Return value: TRUE if success, FALSE otherwise.
266 tree_store_load(void)
268 char *name;
269 int retval;
271 name = mhl_str_dir_plus_file(home_dir, MC_TREE);
272 retval = tree_store_load_from(name);
273 g_free(name);
275 return retval;
278 static char *
279 encode(const char *string)
281 int special_chars;
282 const char *p;
283 char *q;
284 char *res;
286 for (special_chars = 0, p = string; *p; p++) {
287 if (*p == '\n' || *p == '\\')
288 special_chars++;
291 res = g_malloc(p - string + special_chars + 1);
292 for (p = string, q = res; *p; p++, q++) {
293 if (*p != '\n' && *p != '\\') {
294 *q = *p;
295 continue;
298 *q++ = '\\';
300 switch (*p) {
301 case '\n':
302 *q = 'n';
303 break;
305 case '\\':
306 *q = '\\';
307 break;
310 *q = 0;
311 return res;
314 /* Saves the tree to the specified filename */
315 static int
316 tree_store_save_to(char *name)
318 tree_entry *current;
319 FILE *file;
321 file = fopen(name, "w");
322 if (!file)
323 return errno;
325 fprintf(file, "%s\n", TREE_SIGNATURE);
327 current = ts.tree_first;
328 while (current) {
329 int i, common;
331 if (vfs_file_is_local(current->name)) {
332 /* Clear-text compression */
333 if (current->prev
334 && (common =
335 str_common(current->prev->name, current->name)) > 2) {
336 char *encoded = encode(current->name + common);
338 i = fprintf(file, "%d:%d %s\n", current->scanned, common,
339 encoded);
340 g_free(encoded);
341 } else {
342 char *encoded = encode(current->name);
344 i = fprintf(file, "%d:%s\n", current->scanned, encoded);
345 g_free(encoded);
348 if (i == EOF) {
349 fprintf(stderr, _("Cannot write to the %s file:\n%s\n"),
350 name, unix_error_string(errno));
351 break;
354 current = current->next;
356 tree_store_dirty(FALSE);
357 fclose(file);
359 return 0;
363 * tree_store_save:
364 * @void:
366 * Saves the tree to the default file in an atomic fashion.
368 * Return value: 0 if success, errno on error.
371 tree_store_save(void)
373 char *tmp;
374 char *name;
375 int retval;
377 tmp = mhl_str_dir_plus_file(home_dir, MC_TREE_TMP);
378 retval = tree_store_save_to(tmp);
380 if (retval) {
381 g_free(tmp);
382 return retval;
385 name = mhl_str_dir_plus_file(home_dir, MC_TREE);
386 retval = rename(tmp, name);
388 g_free(tmp);
389 g_free(name);
391 if (retval)
392 return errno;
393 else
394 return 0;
397 static tree_entry *
398 tree_store_add_entry(const char *name)
400 int flag = -1;
401 tree_entry *current = ts.tree_first;
402 tree_entry *old = NULL;
403 tree_entry *new;
404 int i, len;
405 int submask = 0;
407 if (ts.tree_last && ts.tree_last->next)
408 abort();
410 /* Search for the correct place */
411 while (current && (flag = pathcmp(current->name, name)) < 0) {
412 old = current;
413 current = current->next;
416 if (flag == 0)
417 return current; /* Already in the list */
419 /* Not in the list -> add it */
420 new = g_new0(tree_entry, 1);
421 if (!current) {
422 /* Append to the end of the list */
423 if (!ts.tree_first) {
424 /* Empty list */
425 ts.tree_first = new;
426 new->prev = NULL;
427 } else {
428 old->next = new;
429 new->prev = old;
431 new->next = NULL;
432 ts.tree_last = new;
433 } else {
434 /* Insert in to the middle of the list */
435 new->prev = old;
436 if (old) {
437 /* Yes, in the middle */
438 new->next = old->next;
439 old->next = new;
440 } else {
441 /* Nope, in the beginning of the list */
442 new->next = ts.tree_first;
443 ts.tree_first = new;
445 new->next->prev = new;
448 /* Calculate attributes */
449 new->name = g_strdup(name);
450 len = strlen(new->name);
451 new->sublevel = 0;
452 for (i = 0; i < len; i++)
453 if (new->name[i] == PATH_SEP) {
454 new->sublevel++;
455 new->subname = new->name + i + 1;
457 if (new->next)
458 submask = new->next->submask;
459 else
460 submask = 0;
461 submask |= 1 << new->sublevel;
462 submask &= (2 << new->sublevel) - 1;
463 new->submask = submask;
464 new->mark = 0;
466 /* Correct the submasks of the previous entries */
467 current = new->prev;
468 while (current && current->sublevel > new->sublevel) {
469 current->submask |= 1 << new->sublevel;
470 current = current->prev;
473 /* The entry has now been added */
475 if (new->sublevel > 1) {
476 /* Let's check if the parent directory is in the tree */
477 char *parent = g_strdup(new->name);
478 int i;
480 for (i = strlen(parent) - 1; i > 1; i--) {
481 if (parent[i] == PATH_SEP) {
482 parent[i] = 0;
483 tree_store_add_entry(parent);
484 break;
487 g_free(parent);
490 tree_store_dirty(TRUE);
491 return new;
494 static Hook *remove_entry_hooks;
496 void
497 tree_store_add_entry_remove_hook(tree_store_remove_fn callback, void *data)
499 add_hook(&remove_entry_hooks, (void (*)(void *)) callback, data);
502 void
503 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback)
505 delete_hook(&remove_entry_hooks, (void (*)(void *)) callback);
508 static void
509 tree_store_notify_remove(tree_entry * entry)
511 Hook *p = remove_entry_hooks;
512 tree_store_remove_fn r;
514 while (p) {
515 r = (tree_store_remove_fn) p->hook_fn;
516 r(entry, p->hook_data);
517 p = p->next;
521 static tree_entry *
522 remove_entry(tree_entry * entry)
524 tree_entry *current = entry->prev;
525 long submask = 0;
526 tree_entry *ret = NULL;
528 tree_store_notify_remove(entry);
530 /* Correct the submasks of the previous entries */
531 if (entry->next)
532 submask = entry->next->submask;
533 while (current && current->sublevel > entry->sublevel) {
534 submask |= 1 << current->sublevel;
535 submask &= (2 << current->sublevel) - 1;
536 current->submask = submask;
537 current = current->prev;
540 /* Unlink the entry from the list */
541 if (entry->prev)
542 entry->prev->next = entry->next;
543 else
544 ts.tree_first = entry->next;
546 if (entry->next)
547 entry->next->prev = entry->prev;
548 else
549 ts.tree_last = entry->prev;
551 /* Free the memory used by the entry */
552 g_free(entry->name);
553 g_free(entry);
555 return ret;
558 void
559 tree_store_remove_entry(const char *name)
561 tree_entry *current, *base, *old;
562 int len;
564 g_return_if_fail(name != NULL);
566 /* Miguel Ugly hack */
567 if (name[0] == PATH_SEP && name[1] == 0)
568 return;
569 /* Miguel Ugly hack end */
571 base = tree_store_whereis(name);
572 if (!base)
573 return; /* Doesn't exist */
575 len = strlen(base->name);
576 current = base->next;
577 while (current
578 && strncmp(current->name, base->name, len) == 0
579 && (current->name[len] == '\0'
580 || current->name[len] == PATH_SEP)) {
581 old = current;
582 current = current->next;
583 remove_entry(old);
585 remove_entry(base);
586 tree_store_dirty(TRUE);
588 return;
591 /* This subdirectory exists -> clear deletion mark */
592 void
593 tree_store_mark_checked(const char *subname)
595 char *name;
596 tree_entry *current, *base;
597 int flag = 1, len;
598 if (!ts.loaded)
599 return;
601 if (ts.check_name == NULL)
602 return;
604 /* Calculate the full name of the subdirectory */
605 if (subname[0] == '.' &&
606 (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
607 return;
608 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
609 name = g_strconcat(PATH_SEP_STR, subname, (char *) NULL);
610 else
611 name = mhl_str_dir_plus_file(ts.check_name, subname);
613 /* Search for the subdirectory */
614 current = ts.check_start;
615 while (current && (flag = pathcmp(current->name, name)) < 0)
616 current = current->next;
618 if (flag != 0) {
619 /* Doesn't exist -> add it */
620 current = tree_store_add_entry(name);
621 ts.add_queue = g_list_prepend(ts.add_queue, g_strdup(name));
623 g_free(name);
625 /* Clear the deletion mark from the subdirectory and its children */
626 base = current;
627 if (base) {
628 len = strlen(base->name);
629 base->mark = 0;
630 current = base->next;
631 while (current
632 && strncmp(current->name, base->name, len) == 0
633 && (current->name[len] == '\0'
634 || current->name[len] == PATH_SEP || len == 1)) {
635 current->mark = 0;
636 current = current->next;
641 /* Mark the subdirectories of the current directory for delete */
642 tree_entry *
643 tree_store_start_check(const char *path)
645 tree_entry *current, *retval;
646 int len;
648 if (!ts.loaded)
649 return NULL;
651 g_return_val_if_fail(ts.check_name == NULL, NULL);
652 ts.check_start = NULL;
654 /* Search for the start of subdirectories */
655 current = tree_store_whereis(path);
656 if (!current) {
657 struct stat s;
659 if (mc_stat(path, &s) == -1)
660 return NULL;
662 if (!S_ISDIR(s.st_mode))
663 return NULL;
665 current = tree_store_add_entry(path);
666 ts.check_name = g_strdup(path);
668 return current;
671 ts.check_name = g_strdup(path);
673 retval = current;
675 /* Mark old subdirectories for delete */
676 ts.check_start = current->next;
677 len = strlen(ts.check_name);
679 current = ts.check_start;
680 while (current
681 && strncmp(current->name, ts.check_name, len) == 0
682 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
683 || len == 1)) {
684 current->mark = 1;
685 current = current->next;
688 return retval;
691 /* Delete subdirectories which still have the deletion mark */
692 void
693 tree_store_end_check(void)
695 tree_entry *current, *old;
696 int len;
697 GList *the_queue, *l;
699 if (!ts.loaded)
700 return;
702 g_return_if_fail(ts.check_name != NULL);
704 /* Check delete marks and delete if found */
705 len = strlen(ts.check_name);
707 current = ts.check_start;
708 while (current
709 && strncmp(current->name, ts.check_name, len) == 0
710 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
711 || len == 1)) {
712 old = current;
713 current = current->next;
714 if (old->mark)
715 remove_entry(old);
718 /* get the stuff in the scan order */
719 ts.add_queue = g_list_reverse(ts.add_queue);
720 the_queue = ts.add_queue;
721 ts.add_queue = NULL;
722 g_free(ts.check_name);
723 ts.check_name = NULL;
725 for (l = the_queue; l; l = l->next) {
726 g_free(l->data);
729 g_list_free(the_queue);
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 static gboolean
750 should_skip_directory(const 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, global_profile_name);
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(const 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 = mhl_str_dir_plus_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;