Merge branch '1396_with_search_engine'
[midnight-commander.git] / src / treestore.c
blob7a9f338f7c7f0d267bcad36f714a5eaf22c669ce
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"
54 #include "fileloc.h"
56 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
58 static struct TreeStore ts;
60 static tree_entry *tree_store_add_entry(const char *name);
62 static void
63 tree_store_dirty(int state)
65 ts.dirty = state;
68 /* Returns the number of common bytes in the strings. */
69 static size_t
70 str_common(const char *s1, const char *s2)
72 size_t result = 0;
74 while (*s1 != '\0' && *s2 != '\0' && *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(const 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 struct 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 *lc_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 lc_name = decode(buffer + 2);
222 len = strlen(lc_name);
223 if (lc_name[0] != PATH_SEP) {
224 /* Clear-text decompression */
225 char *s = strtok(lc_name, " ");
227 if (s) {
228 common = atoi(s);
229 different = strtok(NULL, "");
230 if (different) {
231 strcpy(oldname + common, different);
232 if (vfs_file_is_local(oldname)) {
233 e = tree_store_add_entry(oldname);
234 e->scanned = scanned;
238 } else {
239 if (vfs_file_is_local(lc_name)) {
240 e = tree_store_add_entry(lc_name);
241 e->scanned = scanned;
243 strcpy(oldname, lc_name);
245 g_free(lc_name);
247 fclose(file);
250 /* Nothing loaded, we add some standard directories */
251 if (!ts.tree_first) {
252 tree_store_add_entry(PATH_SEP_STR);
253 tree_store_rescan(PATH_SEP_STR);
254 ts.loaded = TRUE;
257 return TRUE;
261 * \fn int tree_store_load(void)
262 * \brief Loads the tree from the default location
263 * \return 1 if success (true), 0 otherwise (false)
266 tree_store_load(void)
268 char *name;
269 int retval;
271 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
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 * \fn int tree_store_save(void)
364 * \brief Saves the tree to the default file in an atomic fashion
365 * \return 0 if success, errno on error
368 tree_store_save(void)
370 char *name;
371 int retval;
373 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
374 mc_util_make_backup_if_possible (name, ".tmp");
376 if ((retval = tree_store_save_to(name)) != 0) {
377 mc_util_restore_from_backup_if_possible (name, ".tmp");
378 g_free(name);
379 return retval;
382 mc_util_unlink_backup_if_possible (name, ".tmp");
383 g_free(name);
384 return 0;
387 static tree_entry *
388 tree_store_add_entry(const char *name)
390 int flag = -1;
391 tree_entry *current = ts.tree_first;
392 tree_entry *old = NULL;
393 tree_entry *new;
394 int i, len;
395 int submask = 0;
397 if (ts.tree_last && ts.tree_last->next)
398 abort();
400 /* Search for the correct place */
401 while (current && (flag = pathcmp(current->name, name)) < 0) {
402 old = current;
403 current = current->next;
406 if (flag == 0)
407 return current; /* Already in the list */
409 /* Not in the list -> add it */
410 new = g_new0(tree_entry, 1);
411 if (!current) {
412 /* Append to the end of the list */
413 if (!ts.tree_first) {
414 /* Empty list */
415 ts.tree_first = new;
416 new->prev = NULL;
417 } else {
418 old->next = new;
419 new->prev = old;
421 new->next = NULL;
422 ts.tree_last = new;
423 } else {
424 /* Insert in to the middle of the list */
425 new->prev = old;
426 if (old) {
427 /* Yes, in the middle */
428 new->next = old->next;
429 old->next = new;
430 } else {
431 /* Nope, in the beginning of the list */
432 new->next = ts.tree_first;
433 ts.tree_first = new;
435 new->next->prev = new;
438 /* Calculate attributes */
439 new->name = g_strdup(name);
440 len = strlen(new->name);
441 new->sublevel = 0;
442 for (i = 0; i < len; i++)
443 if (new->name[i] == PATH_SEP) {
444 new->sublevel++;
445 new->subname = new->name + i + 1;
447 if (new->next)
448 submask = new->next->submask;
449 else
450 submask = 0;
451 submask |= 1 << new->sublevel;
452 submask &= (2 << new->sublevel) - 1;
453 new->submask = submask;
454 new->mark = 0;
456 /* Correct the submasks of the previous entries */
457 current = new->prev;
458 while (current && current->sublevel > new->sublevel) {
459 current->submask |= 1 << new->sublevel;
460 current = current->prev;
463 /* The entry has now been added */
465 if (new->sublevel > 1) {
466 /* Let's check if the parent directory is in the tree */
467 char *parent = g_strdup(new->name);
469 for (i = strlen(parent) - 1; i > 1; i--) {
470 if (parent[i] == PATH_SEP) {
471 parent[i] = 0;
472 tree_store_add_entry(parent);
473 break;
476 g_free(parent);
479 tree_store_dirty(TRUE);
480 return new;
483 static Hook *remove_entry_hooks;
485 void
486 tree_store_add_entry_remove_hook(tree_store_remove_fn callback, void *data)
488 add_hook(&remove_entry_hooks, (void (*)(void *)) callback, data);
491 void
492 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback)
494 delete_hook(&remove_entry_hooks, (void (*)(void *)) callback);
497 static void
498 tree_store_notify_remove(tree_entry * entry)
500 Hook *p = remove_entry_hooks;
501 tree_store_remove_fn r;
503 while (p) {
504 r = (tree_store_remove_fn) p->hook_fn;
505 r(entry, p->hook_data);
506 p = p->next;
510 static tree_entry *
511 remove_entry(tree_entry * entry)
513 tree_entry *current = entry->prev;
514 long submask = 0;
515 tree_entry *ret = NULL;
517 tree_store_notify_remove(entry);
519 /* Correct the submasks of the previous entries */
520 if (entry->next)
521 submask = entry->next->submask;
522 while (current && current->sublevel > entry->sublevel) {
523 submask |= 1 << current->sublevel;
524 submask &= (2 << current->sublevel) - 1;
525 current->submask = submask;
526 current = current->prev;
529 /* Unlink the entry from the list */
530 if (entry->prev)
531 entry->prev->next = entry->next;
532 else
533 ts.tree_first = entry->next;
535 if (entry->next)
536 entry->next->prev = entry->prev;
537 else
538 ts.tree_last = entry->prev;
540 /* Free the memory used by the entry */
541 g_free(entry->name);
542 g_free(entry);
544 return ret;
547 void
548 tree_store_remove_entry(const char *name)
550 tree_entry *current, *base, *old;
551 int len;
553 g_return_if_fail(name != NULL);
555 /* Miguel Ugly hack */
556 if (name[0] == PATH_SEP && name[1] == 0)
557 return;
558 /* Miguel Ugly hack end */
560 base = tree_store_whereis(name);
561 if (!base)
562 return; /* Doesn't exist */
564 len = strlen(base->name);
565 current = base->next;
566 while (current
567 && strncmp(current->name, base->name, len) == 0
568 && (current->name[len] == '\0'
569 || 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, (char *) 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'
623 || current->name[len] == PATH_SEP || len == 1)) {
624 current->mark = 0;
625 current = current->next;
630 /* Mark the subdirectories of the current directory for delete */
631 tree_entry *
632 tree_store_start_check(const char *path)
634 tree_entry *current, *retval;
635 int len;
637 if (!ts.loaded)
638 return NULL;
640 g_return_val_if_fail(ts.check_name == NULL, NULL);
641 ts.check_start = NULL;
643 /* Search for the start of subdirectories */
644 current = tree_store_whereis(path);
645 if (!current) {
646 struct stat s;
648 if (mc_stat(path, &s) == -1)
649 return NULL;
651 if (!S_ISDIR(s.st_mode))
652 return NULL;
654 current = tree_store_add_entry(path);
655 ts.check_name = g_strdup(path);
657 return current;
660 ts.check_name = g_strdup(path);
662 retval = current;
664 /* Mark old subdirectories for delete */
665 ts.check_start = current->next;
666 len = strlen(ts.check_name);
668 current = ts.check_start;
669 while (current
670 && strncmp(current->name, ts.check_name, len) == 0
671 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
672 || len == 1)) {
673 current->mark = 1;
674 current = current->next;
677 return retval;
680 /* Delete subdirectories which still have the deletion mark */
681 void
682 tree_store_end_check(void)
684 tree_entry *current, *old;
685 int len;
686 GList *the_queue, *l;
688 if (!ts.loaded)
689 return;
691 g_return_if_fail(ts.check_name != NULL);
693 /* Check delete marks and delete if found */
694 len = strlen(ts.check_name);
696 current = ts.check_start;
697 while (current
698 && strncmp(current->name, ts.check_name, len) == 0
699 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
700 || len == 1)) {
701 old = current;
702 current = current->next;
703 if (old->mark)
704 remove_entry(old);
707 /* get the stuff in the scan order */
708 ts.add_queue = g_list_reverse(ts.add_queue);
709 the_queue = ts.add_queue;
710 ts.add_queue = NULL;
711 g_free(ts.check_name);
712 ts.check_name = NULL;
714 for (l = the_queue; l; l = l->next) {
715 g_free(l->data);
718 g_list_free(the_queue);
721 static void
722 process_special_dirs(GList ** special_dirs, char *file)
724 gchar **buffers, **start_buff;
725 mc_config_t *cfg;
726 gsize buffers_len;
728 cfg = mc_config_init(file);
729 if (cfg == NULL)
730 return;
732 start_buff = buffers = mc_config_get_string_list(cfg, "Special dirs", "list", &buffers_len);
733 if (buffers == NULL){
734 mc_config_deinit(cfg);
735 return;
738 while(*buffers) {
739 *special_dirs = g_list_prepend(*special_dirs, g_strdup(*buffers));
740 buffers++;
742 g_strfreev(start_buff);
743 mc_config_deinit(cfg);
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;