Ticket #380: fix of menu colors in BW mode and pseudo-graphics symbols.
[midnight-commander.git] / src / treestore.c
blobc24985177a357554eed7bc42611e37f3c3cd7e4a
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, 2009
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 /** \file treestore.c
35 * \brief Source: tree store
37 * Contains a storage of the file system tree representation.
40 #include <config.h>
42 #include <errno.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <unistd.h>
50 #include "global.h"
51 #include "treestore.h"
52 #include "../src/mcconfig/mcconfig.h"
53 #include "setup.h"
55 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
57 static struct TreeStore ts;
59 static tree_entry *tree_store_add_entry(const char *name);
61 static void
62 tree_store_dirty(int state)
64 ts.dirty = state;
67 /* Returns the number of common bytes in the strings. */
68 static size_t
69 str_common(const char *s1, const char *s2)
71 size_t result = 0;
73 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
74 result++;
75 return result;
78 /* The directory names are arranged in a single linked list in the same
79 order as they are displayed. When the tree is displayed the expected
80 order is like this:
82 /bin
83 /etc
84 /etc/X11
85 /etc/rc.d
86 /etc.old/X11
87 /etc.old/rc.d
88 /usr
90 i.e. the required collating sequence when comparing two directory names is
91 '\0' < PATH_SEP < all-other-characters-in-encoding-order
93 Since strcmp doesn't fulfil this requirement we use pathcmp when
94 inserting directory names into the list. The meaning of the return value
95 of pathcmp and strcmp are the same (an integer less than, equal to, or
96 greater than zero if p1 is found to be less than, to match, or be greater
97 than p2.
99 static int
100 pathcmp(const char *p1, const char *p2)
102 for (; *p1 == *p2; p1++, p2++)
103 if (*p1 == '\0')
104 return 0;
106 if (*p1 == '\0')
107 return -1;
108 if (*p2 == '\0')
109 return 1;
110 if (*p1 == PATH_SEP)
111 return -1;
112 if (*p2 == PATH_SEP)
113 return 1;
114 return (*p1 - *p2);
117 /* Searches for specified directory */
118 tree_entry *
119 tree_store_whereis(const char *name)
121 tree_entry *current = ts.tree_first;
122 int flag = -1;
124 while (current && (flag = pathcmp(current->name, name)) < 0)
125 current = current->next;
127 if (flag == 0)
128 return current;
129 else
130 return NULL;
133 struct TreeStore *
134 tree_store_get(void)
136 return &ts;
139 static char *
140 decode(char *buffer)
142 char *res = g_strdup(buffer);
143 char *p, *q;
145 for (p = q = res; *p; p++, q++) {
146 if (*p == '\n') {
147 *q = 0;
148 return res;
151 if (*p != '\\') {
152 *q = *p;
153 continue;
156 p++;
158 switch (*p) {
159 case 'n':
160 *q = '\n';
161 break;
162 case '\\':
163 *q = '\\';
164 break;
167 *q = *p;
169 return res;
172 /* Loads the tree store from the specified filename */
173 static int
174 tree_store_load_from(char *name)
176 FILE *file;
177 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
178 char *different;
179 int len, common;
180 int do_load;
182 g_return_val_if_fail(name != NULL, FALSE);
184 if (ts.loaded)
185 return TRUE;
187 file = fopen(name, "r");
189 if (file) {
190 fgets(buffer, sizeof(buffer), file);
192 if (strncmp(buffer, TREE_SIGNATURE, strlen(TREE_SIGNATURE)) != 0) {
193 fclose(file);
194 do_load = FALSE;
195 } else
196 do_load = TRUE;
197 } else
198 do_load = FALSE;
200 if (do_load) {
201 ts.loaded = TRUE;
203 /* File open -> read contents */
204 oldname[0] = 0;
205 while (fgets(buffer, MC_MAXPATHLEN, file)) {
206 tree_entry *e;
207 int scanned;
208 char *name;
210 /* Skip invalid records */
211 if ((buffer[0] != '0' && buffer[0] != '1'))
212 continue;
214 if (buffer[1] != ':')
215 continue;
217 scanned = buffer[0] == '1';
219 name = decode(buffer + 2);
221 len = strlen(name);
222 if (name[0] != PATH_SEP) {
223 /* Clear-text decompression */
224 char *s = strtok(name, " ");
226 if (s) {
227 common = atoi(s);
228 different = strtok(NULL, "");
229 if (different) {
230 strcpy(oldname + common, different);
231 if (vfs_file_is_local(oldname)) {
232 e = tree_store_add_entry(oldname);
233 e->scanned = scanned;
237 } else {
238 if (vfs_file_is_local(name)) {
239 e = tree_store_add_entry(name);
240 e->scanned = scanned;
242 strcpy(oldname, name);
244 g_free(name);
246 fclose(file);
249 /* Nothing loaded, we add some standard directories */
250 if (!ts.tree_first) {
251 tree_store_add_entry(PATH_SEP_STR);
252 tree_store_rescan(PATH_SEP_STR);
253 ts.loaded = TRUE;
256 return TRUE;
260 * \fn int tree_store_load(void)
261 * \brief Loads the tree from the default location
262 * \return 1 if success (true), 0 otherwise (false)
265 tree_store_load(void)
267 char *name;
268 int retval;
270 name = concat_dir_and_file(home_dir, MC_TREE);
271 retval = tree_store_load_from(name);
272 g_free(name);
274 return retval;
277 static char *
278 encode(const char *string)
280 int special_chars;
281 const char *p;
282 char *q;
283 char *res;
285 for (special_chars = 0, p = string; *p; p++) {
286 if (*p == '\n' || *p == '\\')
287 special_chars++;
290 res = g_malloc(p - string + special_chars + 1);
291 for (p = string, q = res; *p; p++, q++) {
292 if (*p != '\n' && *p != '\\') {
293 *q = *p;
294 continue;
297 *q++ = '\\';
299 switch (*p) {
300 case '\n':
301 *q = 'n';
302 break;
304 case '\\':
305 *q = '\\';
306 break;
309 *q = 0;
310 return res;
313 /* Saves the tree to the specified filename */
314 static int
315 tree_store_save_to(char *name)
317 tree_entry *current;
318 FILE *file;
320 file = fopen(name, "w");
321 if (!file)
322 return errno;
324 fprintf(file, "%s\n", TREE_SIGNATURE);
326 current = ts.tree_first;
327 while (current) {
328 int i, common;
330 if (vfs_file_is_local(current->name)) {
331 /* Clear-text compression */
332 if (current->prev
333 && (common =
334 str_common(current->prev->name, current->name)) > 2) {
335 char *encoded = encode(current->name + common);
337 i = fprintf(file, "%d:%d %s\n", current->scanned, common,
338 encoded);
339 g_free(encoded);
340 } else {
341 char *encoded = encode(current->name);
343 i = fprintf(file, "%d:%s\n", current->scanned, encoded);
344 g_free(encoded);
347 if (i == EOF) {
348 fprintf(stderr, _("Cannot write to the %s file:\n%s\n"),
349 name, unix_error_string(errno));
350 break;
353 current = current->next;
355 tree_store_dirty(FALSE);
356 fclose(file);
358 return 0;
362 * \fn int tree_store_save(void)
363 * \brief Saves the tree to the default file in an atomic fashion
364 * \return 0 if success, errno on error
367 tree_store_save(void)
369 char *tmp;
370 char *name;
371 int retval;
373 tmp = concat_dir_and_file(home_dir, MC_TREE_TMP);
374 retval = tree_store_save_to(tmp);
376 if (retval) {
377 g_free(tmp);
378 return retval;
381 name = concat_dir_and_file(home_dir, MC_TREE);
382 retval = rename(tmp, name);
384 g_free(tmp);
385 g_free(name);
387 if (retval)
388 return errno;
389 else
390 return 0;
393 static tree_entry *
394 tree_store_add_entry(const char *name)
396 int flag = -1;
397 tree_entry *current = ts.tree_first;
398 tree_entry *old = NULL;
399 tree_entry *new;
400 int i, len;
401 int submask = 0;
403 if (ts.tree_last && ts.tree_last->next)
404 abort();
406 /* Search for the correct place */
407 while (current && (flag = pathcmp(current->name, name)) < 0) {
408 old = current;
409 current = current->next;
412 if (flag == 0)
413 return current; /* Already in the list */
415 /* Not in the list -> add it */
416 new = g_new0(tree_entry, 1);
417 if (!current) {
418 /* Append to the end of the list */
419 if (!ts.tree_first) {
420 /* Empty list */
421 ts.tree_first = new;
422 new->prev = NULL;
423 } else {
424 old->next = new;
425 new->prev = old;
427 new->next = NULL;
428 ts.tree_last = new;
429 } else {
430 /* Insert in to the middle of the list */
431 new->prev = old;
432 if (old) {
433 /* Yes, in the middle */
434 new->next = old->next;
435 old->next = new;
436 } else {
437 /* Nope, in the beginning of the list */
438 new->next = ts.tree_first;
439 ts.tree_first = new;
441 new->next->prev = new;
444 /* Calculate attributes */
445 new->name = g_strdup(name);
446 len = strlen(new->name);
447 new->sublevel = 0;
448 for (i = 0; i < len; i++)
449 if (new->name[i] == PATH_SEP) {
450 new->sublevel++;
451 new->subname = new->name + i + 1;
453 if (new->next)
454 submask = new->next->submask;
455 else
456 submask = 0;
457 submask |= 1 << new->sublevel;
458 submask &= (2 << new->sublevel) - 1;
459 new->submask = submask;
460 new->mark = 0;
462 /* Correct the submasks of the previous entries */
463 current = new->prev;
464 while (current && current->sublevel > new->sublevel) {
465 current->submask |= 1 << new->sublevel;
466 current = current->prev;
469 /* The entry has now been added */
471 if (new->sublevel > 1) {
472 /* Let's check if the parent directory is in the tree */
473 char *parent = g_strdup(new->name);
474 int i;
476 for (i = strlen(parent) - 1; i > 1; i--) {
477 if (parent[i] == PATH_SEP) {
478 parent[i] = 0;
479 tree_store_add_entry(parent);
480 break;
483 g_free(parent);
486 tree_store_dirty(TRUE);
487 return new;
490 static Hook *remove_entry_hooks;
492 void
493 tree_store_add_entry_remove_hook(tree_store_remove_fn callback, void *data)
495 add_hook(&remove_entry_hooks, (void (*)(void *)) callback, data);
498 void
499 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback)
501 delete_hook(&remove_entry_hooks, (void (*)(void *)) callback);
504 static void
505 tree_store_notify_remove(tree_entry * entry)
507 Hook *p = remove_entry_hooks;
508 tree_store_remove_fn r;
510 while (p) {
511 r = (tree_store_remove_fn) p->hook_fn;
512 r(entry, p->hook_data);
513 p = p->next;
517 static tree_entry *
518 remove_entry(tree_entry * entry)
520 tree_entry *current = entry->prev;
521 long submask = 0;
522 tree_entry *ret = NULL;
524 tree_store_notify_remove(entry);
526 /* Correct the submasks of the previous entries */
527 if (entry->next)
528 submask = entry->next->submask;
529 while (current && current->sublevel > entry->sublevel) {
530 submask |= 1 << current->sublevel;
531 submask &= (2 << current->sublevel) - 1;
532 current->submask = submask;
533 current = current->prev;
536 /* Unlink the entry from the list */
537 if (entry->prev)
538 entry->prev->next = entry->next;
539 else
540 ts.tree_first = entry->next;
542 if (entry->next)
543 entry->next->prev = entry->prev;
544 else
545 ts.tree_last = entry->prev;
547 /* Free the memory used by the entry */
548 g_free(entry->name);
549 g_free(entry);
551 return ret;
554 void
555 tree_store_remove_entry(const char *name)
557 tree_entry *current, *base, *old;
558 int len;
560 g_return_if_fail(name != NULL);
562 /* Miguel Ugly hack */
563 if (name[0] == PATH_SEP && name[1] == 0)
564 return;
565 /* Miguel Ugly hack end */
567 base = tree_store_whereis(name);
568 if (!base)
569 return; /* Doesn't exist */
571 len = strlen(base->name);
572 current = base->next;
573 while (current
574 && strncmp(current->name, base->name, len) == 0
575 && (current->name[len] == '\0'
576 || current->name[len] == PATH_SEP)) {
577 old = current;
578 current = current->next;
579 remove_entry(old);
581 remove_entry(base);
582 tree_store_dirty(TRUE);
584 return;
587 /* This subdirectory exists -> clear deletion mark */
588 void
589 tree_store_mark_checked(const char *subname)
591 char *name;
592 tree_entry *current, *base;
593 int flag = 1, len;
594 if (!ts.loaded)
595 return;
597 if (ts.check_name == NULL)
598 return;
600 /* Calculate the full name of the subdirectory */
601 if (subname[0] == '.' &&
602 (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
603 return;
604 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
605 name = g_strconcat(PATH_SEP_STR, subname, (char *) NULL);
606 else
607 name = concat_dir_and_file(ts.check_name, subname);
609 /* Search for the subdirectory */
610 current = ts.check_start;
611 while (current && (flag = pathcmp(current->name, name)) < 0)
612 current = current->next;
614 if (flag != 0) {
615 /* Doesn't exist -> add it */
616 current = tree_store_add_entry(name);
617 ts.add_queue = g_list_prepend(ts.add_queue, g_strdup(name));
619 g_free(name);
621 /* Clear the deletion mark from the subdirectory and its children */
622 base = current;
623 if (base) {
624 len = strlen(base->name);
625 base->mark = 0;
626 current = base->next;
627 while (current
628 && strncmp(current->name, base->name, len) == 0
629 && (current->name[len] == '\0'
630 || current->name[len] == PATH_SEP || len == 1)) {
631 current->mark = 0;
632 current = current->next;
637 /* Mark the subdirectories of the current directory for delete */
638 tree_entry *
639 tree_store_start_check(const char *path)
641 tree_entry *current, *retval;
642 int len;
644 if (!ts.loaded)
645 return NULL;
647 g_return_val_if_fail(ts.check_name == NULL, NULL);
648 ts.check_start = NULL;
650 /* Search for the start of subdirectories */
651 current = tree_store_whereis(path);
652 if (!current) {
653 struct stat s;
655 if (mc_stat(path, &s) == -1)
656 return NULL;
658 if (!S_ISDIR(s.st_mode))
659 return NULL;
661 current = tree_store_add_entry(path);
662 ts.check_name = g_strdup(path);
664 return current;
667 ts.check_name = g_strdup(path);
669 retval = current;
671 /* Mark old subdirectories for delete */
672 ts.check_start = current->next;
673 len = strlen(ts.check_name);
675 current = ts.check_start;
676 while (current
677 && strncmp(current->name, ts.check_name, len) == 0
678 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
679 || len == 1)) {
680 current->mark = 1;
681 current = current->next;
684 return retval;
687 /* Delete subdirectories which still have the deletion mark */
688 void
689 tree_store_end_check(void)
691 tree_entry *current, *old;
692 int len;
693 GList *the_queue, *l;
695 if (!ts.loaded)
696 return;
698 g_return_if_fail(ts.check_name != NULL);
700 /* Check delete marks and delete if found */
701 len = strlen(ts.check_name);
703 current = ts.check_start;
704 while (current
705 && strncmp(current->name, ts.check_name, len) == 0
706 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
707 || len == 1)) {
708 old = current;
709 current = current->next;
710 if (old->mark)
711 remove_entry(old);
714 /* get the stuff in the scan order */
715 ts.add_queue = g_list_reverse(ts.add_queue);
716 the_queue = ts.add_queue;
717 ts.add_queue = NULL;
718 g_free(ts.check_name);
719 ts.check_name = NULL;
721 for (l = the_queue; l; l = l->next) {
722 g_free(l->data);
725 g_list_free(the_queue);
728 static void
729 process_special_dirs(GList ** special_dirs, char *file)
731 gchar **buffers, **start_buff;
732 mc_config_t *cfg;
733 gsize buffers_len;
735 cfg = mc_config_init(file);
736 if (cfg == NULL)
737 return;
739 start_buff = buffers = mc_config_get_string_list(cfg, "Special dirs", "list", &buffers_len);
740 if (buffers == NULL){
741 mc_config_deinit(cfg);
742 return;
745 while(*buffers) {
746 *special_dirs = g_list_prepend(*special_dirs, g_strdup(*buffers));
747 buffers++;
749 g_strfreev(start_buff);
750 mc_config_deinit(cfg);
753 static gboolean
754 should_skip_directory(const char *dir)
756 static GList *special_dirs;
757 GList *l;
758 static int loaded;
760 if (loaded == 0) {
761 loaded = 1;
762 setup_init();
763 process_special_dirs(&special_dirs, profile_name);
764 process_special_dirs(&special_dirs, global_profile_name);
767 for (l = special_dirs; l; l = l->next) {
768 if (strncmp(dir, l->data, strlen(l->data)) == 0)
769 return TRUE;
771 return FALSE;
774 tree_entry *
775 tree_store_rescan(const char *dir)
777 DIR *dirp;
778 struct dirent *dp;
779 struct stat buf;
780 tree_entry *entry;
782 if (should_skip_directory(dir)) {
783 entry = tree_store_add_entry(dir);
784 entry->scanned = 1;
786 return entry;
789 entry = tree_store_start_check(dir);
791 if (!entry)
792 return NULL;
794 dirp = mc_opendir(dir);
795 if (dirp) {
796 for (dp = mc_readdir(dirp); dp; dp = mc_readdir(dirp)) {
797 char *full_name;
799 if (dp->d_name[0] == '.') {
800 if (dp->d_name[1] == 0
801 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
802 continue;
805 full_name = concat_dir_and_file(dir, dp->d_name);
806 if (mc_lstat(full_name, &buf) != -1) {
807 if (S_ISDIR(buf.st_mode))
808 tree_store_mark_checked(dp->d_name);
810 g_free(full_name);
812 mc_closedir(dirp);
814 tree_store_end_check();
815 entry->scanned = 1;
817 return entry;