Changes into src directory:
[midnight-commander.git] / src / treestore.c
blob6b11a4d3c4ddcfb7c4683ac57f4326a5060fba20
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 "lib/global.h"
51 #include "lib/mcconfig.h"
52 #include "lib/vfs/mc-vfs/vfs.h"
53 #include "lib/fileloc.h"
55 #include "treestore.h"
56 #include "setup.h"
58 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
60 static struct TreeStore ts;
62 static tree_entry *tree_store_add_entry(const char *name);
64 static void
65 tree_store_dirty(int state)
67 ts.dirty = state;
70 /* Returns the number of common bytes in the strings. */
71 static size_t
72 str_common(const char *s1, const char *s2)
74 size_t result = 0;
76 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
77 result++;
78 return result;
81 /* The directory names are arranged in a single linked list in the same
82 order as they are displayed. When the tree is displayed the expected
83 order is like this:
85 /bin
86 /etc
87 /etc/X11
88 /etc/rc.d
89 /etc.old/X11
90 /etc.old/rc.d
91 /usr
93 i.e. the required collating sequence when comparing two directory names is
94 '\0' < PATH_SEP < all-other-characters-in-encoding-order
96 Since strcmp doesn't fulfil this requirement we use pathcmp when
97 inserting directory names into the list. The meaning of the return value
98 of pathcmp and strcmp are the same (an integer less than, equal to, or
99 greater than zero if p1 is found to be less than, to match, or be greater
100 than p2.
102 static int
103 pathcmp(const char *p1, const char *p2)
105 for (; *p1 == *p2; p1++, p2++)
106 if (*p1 == '\0')
107 return 0;
109 if (*p1 == '\0')
110 return -1;
111 if (*p2 == '\0')
112 return 1;
113 if (*p1 == PATH_SEP)
114 return -1;
115 if (*p2 == PATH_SEP)
116 return 1;
117 return (*p1 - *p2);
120 /* Searches for specified directory */
121 tree_entry *
122 tree_store_whereis(const char *name)
124 tree_entry *current = ts.tree_first;
125 int flag = -1;
127 while (current && (flag = pathcmp(current->name, name)) < 0)
128 current = current->next;
130 if (flag == 0)
131 return current;
132 else
133 return NULL;
136 struct TreeStore *
137 tree_store_get(void)
139 return &ts;
142 static char *
143 decode(char *buffer)
145 char *res = g_strdup(buffer);
146 char *p, *q;
148 for (p = q = res; *p; p++, q++) {
149 if (*p == '\n') {
150 *q = 0;
151 return res;
154 if (*p != '\\') {
155 *q = *p;
156 continue;
159 p++;
161 switch (*p) {
162 case 'n':
163 *q = '\n';
164 break;
165 case '\\':
166 *q = '\\';
167 break;
170 *q = *p;
172 return res;
175 /* Loads the tree store from the specified filename */
176 static int
177 tree_store_load_from(char *name)
179 FILE *file;
180 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
181 char *different;
182 int len, common;
183 int do_load;
185 g_return_val_if_fail(name != NULL, FALSE);
187 if (ts.loaded)
188 return TRUE;
190 file = fopen(name, "r");
192 if (file) {
193 if ( fgets(buffer, sizeof(buffer), file) != NULL )
195 if (strncmp(buffer, TREE_SIGNATURE, strlen(TREE_SIGNATURE)) != 0) {
196 fclose(file);
197 do_load = FALSE;
198 } else
199 do_load = TRUE;
201 else
202 do_load = FALSE;
203 } else
204 do_load = FALSE;
206 if (do_load) {
207 ts.loaded = TRUE;
209 /* File open -> read contents */
210 oldname[0] = 0;
211 while (fgets(buffer, MC_MAXPATHLEN, file)) {
212 tree_entry *e;
213 int scanned;
214 char *lc_name;
216 /* Skip invalid records */
217 if ((buffer[0] != '0' && buffer[0] != '1'))
218 continue;
220 if (buffer[1] != ':')
221 continue;
223 scanned = buffer[0] == '1';
225 lc_name = decode(buffer + 2);
227 len = strlen(lc_name);
228 if (lc_name[0] != PATH_SEP) {
229 /* Clear-text decompression */
230 char *s = strtok(lc_name, " ");
232 if (s) {
233 common = atoi(s);
234 different = strtok(NULL, "");
235 if (different) {
236 strcpy(oldname + common, different);
237 if (vfs_file_is_local(oldname)) {
238 e = tree_store_add_entry(oldname);
239 e->scanned = scanned;
243 } else {
244 if (vfs_file_is_local(lc_name)) {
245 e = tree_store_add_entry(lc_name);
246 e->scanned = scanned;
248 strcpy(oldname, lc_name);
250 g_free(lc_name);
252 fclose(file);
255 /* Nothing loaded, we add some standard directories */
256 if (!ts.tree_first) {
257 tree_store_add_entry(PATH_SEP_STR);
258 tree_store_rescan(PATH_SEP_STR);
259 ts.loaded = TRUE;
262 return TRUE;
266 * \fn int tree_store_load(void)
267 * \brief Loads the tree from the default location
268 * \return 1 if success (true), 0 otherwise (false)
271 tree_store_load(void)
273 char *name;
274 int retval;
276 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
277 retval = tree_store_load_from(name);
278 g_free(name);
280 return retval;
283 static char *
284 encode(const char *string)
286 int special_chars;
287 const char *p;
288 char *q;
289 char *res;
291 for (special_chars = 0, p = string; *p; p++) {
292 if (*p == '\n' || *p == '\\')
293 special_chars++;
296 res = g_malloc(p - string + special_chars + 1);
297 for (p = string, q = res; *p; p++, q++) {
298 if (*p != '\n' && *p != '\\') {
299 *q = *p;
300 continue;
303 *q++ = '\\';
305 switch (*p) {
306 case '\n':
307 *q = 'n';
308 break;
310 case '\\':
311 *q = '\\';
312 break;
315 *q = 0;
316 return res;
319 /* Saves the tree to the specified filename */
320 static int
321 tree_store_save_to(char *name)
323 tree_entry *current;
324 FILE *file;
326 file = fopen(name, "w");
327 if (!file)
328 return errno;
330 fprintf(file, "%s\n", TREE_SIGNATURE);
332 current = ts.tree_first;
333 while (current) {
334 int i, common;
336 if (vfs_file_is_local(current->name)) {
337 /* Clear-text compression */
338 if (current->prev
339 && (common =
340 str_common(current->prev->name, current->name)) > 2) {
341 char *encoded = encode(current->name + common);
343 i = fprintf(file, "%d:%d %s\n", current->scanned, common,
344 encoded);
345 g_free(encoded);
346 } else {
347 char *encoded = encode(current->name);
349 i = fprintf(file, "%d:%s\n", current->scanned, encoded);
350 g_free(encoded);
353 if (i == EOF) {
354 fprintf(stderr, _("Cannot write to the %s file:\n%s\n"),
355 name, unix_error_string(errno));
356 break;
359 current = current->next;
361 tree_store_dirty(FALSE);
362 fclose(file);
364 return 0;
368 * \fn int tree_store_save(void)
369 * \brief Saves the tree to the default file in an atomic fashion
370 * \return 0 if success, errno on error
373 tree_store_save(void)
375 char *name;
376 int retval;
378 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
379 mc_util_make_backup_if_possible (name, ".tmp");
381 if ((retval = tree_store_save_to(name)) != 0) {
382 mc_util_restore_from_backup_if_possible (name, ".tmp");
383 g_free(name);
384 return retval;
387 mc_util_unlink_backup_if_possible (name, ".tmp");
388 g_free(name);
389 return 0;
392 static tree_entry *
393 tree_store_add_entry(const char *name)
395 int flag = -1;
396 tree_entry *current = ts.tree_first;
397 tree_entry *old = NULL;
398 tree_entry *new;
399 int i, len;
400 int submask = 0;
402 if (ts.tree_last && ts.tree_last->next)
403 abort();
405 /* Search for the correct place */
406 while (current && (flag = pathcmp(current->name, name)) < 0) {
407 old = current;
408 current = current->next;
411 if (flag == 0)
412 return current; /* Already in the list */
414 /* Not in the list -> add it */
415 new = g_new0(tree_entry, 1);
416 if (!current) {
417 /* Append to the end of the list */
418 if (!ts.tree_first) {
419 /* Empty list */
420 ts.tree_first = new;
421 new->prev = NULL;
422 } else {
423 old->next = new;
424 new->prev = old;
426 new->next = NULL;
427 ts.tree_last = new;
428 } else {
429 /* Insert in to the middle of the list */
430 new->prev = old;
431 if (old) {
432 /* Yes, in the middle */
433 new->next = old->next;
434 old->next = new;
435 } else {
436 /* Nope, in the beginning of the list */
437 new->next = ts.tree_first;
438 ts.tree_first = new;
440 new->next->prev = new;
443 /* Calculate attributes */
444 new->name = g_strdup(name);
445 len = strlen(new->name);
446 new->sublevel = 0;
447 for (i = 0; i < len; i++)
448 if (new->name[i] == PATH_SEP) {
449 new->sublevel++;
450 new->subname = new->name + i + 1;
452 if (new->next)
453 submask = new->next->submask;
454 else
455 submask = 0;
456 submask |= 1 << new->sublevel;
457 submask &= (2 << new->sublevel) - 1;
458 new->submask = submask;
459 new->mark = 0;
461 /* Correct the submasks of the previous entries */
462 current = new->prev;
463 while (current && current->sublevel > new->sublevel) {
464 current->submask |= 1 << new->sublevel;
465 current = current->prev;
468 /* The entry has now been added */
470 if (new->sublevel > 1) {
471 /* Let's check if the parent directory is in the tree */
472 char *parent = g_strdup(new->name);
474 for (i = strlen(parent) - 1; i > 1; i--) {
475 if (parent[i] == PATH_SEP) {
476 parent[i] = 0;
477 tree_store_add_entry(parent);
478 break;
481 g_free(parent);
484 tree_store_dirty(TRUE);
485 return new;
488 static Hook *remove_entry_hooks;
490 void
491 tree_store_add_entry_remove_hook(tree_store_remove_fn callback, void *data)
493 add_hook(&remove_entry_hooks, (void (*)(void *)) callback, data);
496 void
497 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback)
499 delete_hook(&remove_entry_hooks, (void (*)(void *)) callback);
502 static void
503 tree_store_notify_remove(tree_entry * entry)
505 Hook *p = remove_entry_hooks;
506 tree_store_remove_fn r;
508 while (p) {
509 r = (tree_store_remove_fn) p->hook_fn;
510 r(entry, p->hook_data);
511 p = p->next;
515 static tree_entry *
516 remove_entry(tree_entry * entry)
518 tree_entry *current = entry->prev;
519 long submask = 0;
520 tree_entry *ret = NULL;
522 tree_store_notify_remove(entry);
524 /* Correct the submasks of the previous entries */
525 if (entry->next)
526 submask = entry->next->submask;
527 while (current && current->sublevel > entry->sublevel) {
528 submask |= 1 << current->sublevel;
529 submask &= (2 << current->sublevel) - 1;
530 current->submask = submask;
531 current = current->prev;
534 /* Unlink the entry from the list */
535 if (entry->prev)
536 entry->prev->next = entry->next;
537 else
538 ts.tree_first = entry->next;
540 if (entry->next)
541 entry->next->prev = entry->prev;
542 else
543 ts.tree_last = entry->prev;
545 /* Free the memory used by the entry */
546 g_free(entry->name);
547 g_free(entry);
549 return ret;
552 void
553 tree_store_remove_entry(const char *name)
555 tree_entry *current, *base, *old;
556 int len;
558 g_return_if_fail(name != NULL);
560 /* Miguel Ugly hack */
561 if (name[0] == PATH_SEP && name[1] == 0)
562 return;
563 /* Miguel Ugly hack end */
565 base = tree_store_whereis(name);
566 if (!base)
567 return; /* Doesn't exist */
569 len = strlen(base->name);
570 current = base->next;
571 while (current
572 && strncmp(current->name, base->name, len) == 0
573 && (current->name[len] == '\0'
574 || current->name[len] == PATH_SEP)) {
575 old = current;
576 current = current->next;
577 remove_entry(old);
579 remove_entry(base);
580 tree_store_dirty(TRUE);
582 return;
585 /* This subdirectory exists -> clear deletion mark */
586 void
587 tree_store_mark_checked(const char *subname)
589 char *name;
590 tree_entry *current, *base;
591 int flag = 1, len;
592 if (!ts.loaded)
593 return;
595 if (ts.check_name == NULL)
596 return;
598 /* Calculate the full name of the subdirectory */
599 if (subname[0] == '.' &&
600 (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
601 return;
602 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
603 name = g_strconcat(PATH_SEP_STR, subname, (char *) NULL);
604 else
605 name = concat_dir_and_file(ts.check_name, subname);
607 /* Search for the subdirectory */
608 current = ts.check_start;
609 while (current && (flag = pathcmp(current->name, name)) < 0)
610 current = current->next;
612 if (flag != 0) {
613 /* Doesn't exist -> add it */
614 current = tree_store_add_entry(name);
615 ts.add_queue = g_list_prepend(ts.add_queue, g_strdup(name));
617 g_free(name);
619 /* Clear the deletion mark from the subdirectory and its children */
620 base = current;
621 if (base) {
622 len = strlen(base->name);
623 base->mark = 0;
624 current = base->next;
625 while (current
626 && strncmp(current->name, base->name, len) == 0
627 && (current->name[len] == '\0'
628 || current->name[len] == PATH_SEP || len == 1)) {
629 current->mark = 0;
630 current = current->next;
635 /* Mark the subdirectories of the current directory for delete */
636 tree_entry *
637 tree_store_start_check(const char *path)
639 tree_entry *current, *retval;
640 int len;
642 if (!ts.loaded)
643 return NULL;
645 g_return_val_if_fail(ts.check_name == NULL, NULL);
646 ts.check_start = NULL;
648 /* Search for the start of subdirectories */
649 current = tree_store_whereis(path);
650 if (!current) {
651 struct stat s;
653 if (mc_stat(path, &s) == -1)
654 return NULL;
656 if (!S_ISDIR(s.st_mode))
657 return NULL;
659 current = tree_store_add_entry(path);
660 ts.check_name = g_strdup(path);
662 return current;
665 ts.check_name = g_strdup(path);
667 retval = current;
669 /* Mark old subdirectories for delete */
670 ts.check_start = current->next;
671 len = strlen(ts.check_name);
673 current = ts.check_start;
674 while (current
675 && strncmp(current->name, ts.check_name, len) == 0
676 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
677 || len == 1)) {
678 current->mark = 1;
679 current = current->next;
682 return retval;
685 /* Delete subdirectories which still have the deletion mark */
686 void
687 tree_store_end_check(void)
689 tree_entry *current, *old;
690 int len;
691 GList *the_queue, *l;
693 if (!ts.loaded)
694 return;
696 g_return_if_fail(ts.check_name != NULL);
698 /* Check delete marks and delete if found */
699 len = strlen(ts.check_name);
701 current = ts.check_start;
702 while (current
703 && strncmp(current->name, ts.check_name, len) == 0
704 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
705 || len == 1)) {
706 old = current;
707 current = current->next;
708 if (old->mark)
709 remove_entry(old);
712 /* get the stuff in the scan order */
713 ts.add_queue = g_list_reverse(ts.add_queue);
714 the_queue = ts.add_queue;
715 ts.add_queue = NULL;
716 g_free(ts.check_name);
717 ts.check_name = NULL;
719 for (l = the_queue; l; l = l->next) {
720 g_free(l->data);
723 g_list_free(the_queue);
726 static void
727 process_special_dirs(GList ** special_dirs, char *file)
729 gchar **buffers, **start_buff;
730 mc_config_t *cfg;
731 gsize buffers_len;
733 cfg = mc_config_init(file);
734 if (cfg == NULL)
735 return;
737 start_buff = buffers = mc_config_get_string_list(cfg, "Special dirs", "list", &buffers_len);
738 if (buffers == NULL){
739 mc_config_deinit(cfg);
740 return;
743 while(*buffers) {
744 *special_dirs = g_list_prepend(*special_dirs, g_strdup(*buffers));
745 buffers++;
747 g_strfreev(start_buff);
748 mc_config_deinit(cfg);
751 static gboolean
752 should_skip_directory(const char *dir)
754 static GList *special_dirs;
755 GList *l;
756 static int loaded;
758 if (loaded == 0) {
759 loaded = 1;
760 setup_init();
761 process_special_dirs(&special_dirs, profile_name);
762 process_special_dirs(&special_dirs, global_profile_name);
765 for (l = special_dirs; l; l = l->next) {
766 if (strncmp(dir, l->data, strlen(l->data)) == 0)
767 return TRUE;
769 return FALSE;
772 tree_entry *
773 tree_store_rescan(const char *dir)
775 DIR *dirp;
776 struct dirent *dp;
777 struct stat buf;
778 tree_entry *entry;
780 if (should_skip_directory(dir)) {
781 entry = tree_store_add_entry(dir);
782 entry->scanned = 1;
784 return entry;
787 entry = tree_store_start_check(dir);
789 if (!entry)
790 return NULL;
792 dirp = mc_opendir(dir);
793 if (dirp) {
794 for (dp = mc_readdir(dirp); dp; dp = mc_readdir(dirp)) {
795 char *full_name;
797 if (dp->d_name[0] == '.') {
798 if (dp->d_name[1] == 0
799 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
800 continue;
803 full_name = concat_dir_and_file(dir, dp->d_name);
804 if (mc_lstat(full_name, &buf) != -1) {
805 if (S_ISDIR(buf.st_mode))
806 tree_store_mark_checked(dp->d_name);
808 g_free(full_name);
810 mc_closedir(dirp);
812 tree_store_end_check();
813 entry->scanned = 1;
815 return entry;