Optimization of ini files load.
[midnight-commander.git] / src / filemanager / treestore.c
blob15942ff00b8f134981495dbca4254c4c94b69775
1 /*
2 Tree Store
3 Contains a storage of the file system tree representation
5 This module has been converted to be a widget.
7 The program load and saves the tree each time the tree widget is
8 created and destroyed. This is required for the future vfs layer,
9 it will be possible to have tree views over virtual file systems.
11 Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2009,
12 2011
13 The Free Software Foundation, Inc.
15 Written by:
16 Janne Kukonlehto, 1994, 1996
17 Norbert Warmuth, 1997
18 Miguel de Icaza, 1996, 1999
20 This file is part of the Midnight Commander.
22 The Midnight Commander is free software: you can redistribute it
23 and/or modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation, either version 3 of the License,
25 or (at your option) any later version.
27 The Midnight Commander is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
32 You should have received a copy of the GNU General Public License
33 along with this program. If not, see <http://www.gnu.org/licenses/>.
36 /** \file treestore.c
37 * \brief Source: tree store
39 * Contains a storage of the file system tree representation.
42 #include <config.h>
44 #include <errno.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <unistd.h>
52 #include "lib/global.h"
53 #include "lib/mcconfig.h"
54 #include "lib/vfs/vfs.h"
55 #include "lib/fileloc.h"
56 #include "lib/strescape.h"
57 #include "lib/hook.h"
58 #include "lib/util.h"
60 #include "src/setup.h"
62 #include "treestore.h"
64 /*** global variables ****************************************************************************/
66 /*** file scope macro definitions ****************************************************************/
68 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
70 /*** file scope type declarations ****************************************************************/
72 /*** file scope variables ************************************************************************/
74 static struct TreeStore ts;
76 static hook_t *remove_entry_hooks;
78 /*** file scope functions ************************************************************************/
79 /* --------------------------------------------------------------------------------------------- */
81 static tree_entry *tree_store_add_entry (const vfs_path_t * name);
83 /* --------------------------------------------------------------------------------------------- */
85 static void
86 tree_store_dirty (int state)
88 ts.dirty = state;
91 /* --------------------------------------------------------------------------------------------- */
92 /**
94 * @return the number of common bytes in the strings.
97 static size_t
98 str_common (const vfs_path_t * s1_vpath, const vfs_path_t * s2_vpath)
100 size_t result = 0;
101 char *s1, *fs1;
102 char *s2, *fs2;
104 s1 = fs1 = vfs_path_to_str (s1_vpath);
105 s2 = fs2 = vfs_path_to_str (s2_vpath);
107 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
108 result++;
110 g_free (fs1);
111 g_free (fs2);
113 return result;
116 /* --------------------------------------------------------------------------------------------- */
117 /** The directory names are arranged in a single linked list in the same
118 * order as they are displayed. When the tree is displayed the expected
119 * order is like this:
121 * /bin
122 * /etc
123 * /etc/X11
124 * /etc/rc.d
125 * /etc.old/X11
126 * /etc.old/rc.d
127 * /usr
129 * i.e. the required collating sequence when comparing two directory names is
130 * '\0' < PATH_SEP < all-other-characters-in-encoding-order
132 * Since strcmp doesn't fulfil this requirement we use pathcmp when
133 * inserting directory names into the list. The meaning of the return value
134 * of pathcmp and strcmp are the same (an integer less than, equal to, or
135 * greater than zero if p1 is found to be less than, to match, or be greater
136 * than p2.
139 static int
140 pathcmp (const vfs_path_t * p1_vpath, const vfs_path_t * p2_vpath)
142 int ret_val;
143 char *p1, *fp1;
144 char *p2, *fp2;
146 p1 = fp1 = vfs_path_to_str (p1_vpath);
147 p2 = fp2 = vfs_path_to_str (p2_vpath);
149 for (; *p1 == *p2; p1++, p2++)
150 if (*p1 == '\0')
152 g_free (fp1);
153 g_free (fp2);
154 return 0;
157 if (*p1 == '\0')
158 ret_val = -1;
159 else if (*p2 == '\0')
160 ret_val = 1;
161 else if (*p1 == PATH_SEP)
162 ret_val = -1;
163 else if (*p2 == PATH_SEP)
164 ret_val = 1;
165 else
166 ret_val = (*p1 - *p2);
168 g_free (fp1);
169 g_free (fp2);
170 return ret_val;
173 /* --------------------------------------------------------------------------------------------- */
175 static char *
176 decode (char *buffer)
178 char *res = g_strdup (buffer);
179 char *p, *q;
181 for (p = q = res; *p; p++, q++)
183 if (*p == '\n')
185 *q = 0;
186 return res;
189 if (*p != '\\')
191 *q = *p;
192 continue;
195 p++;
197 switch (*p)
199 case 'n':
200 *q = '\n';
201 break;
202 case '\\':
203 *q = '\\';
204 break;
207 *q = *p;
209 return res;
212 /* --------------------------------------------------------------------------------------------- */
213 /** Loads the tree store from the specified filename */
215 static int
216 tree_store_load_from (char *name)
218 FILE *file;
219 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
220 char *different;
221 int common;
222 int do_load;
224 g_return_val_if_fail (name != NULL, FALSE);
226 if (ts.loaded)
227 return TRUE;
229 file = fopen (name, "r");
231 if (file)
233 if (fgets (buffer, sizeof (buffer), file) != NULL)
235 if (strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0)
237 fclose (file);
238 do_load = FALSE;
240 else
241 do_load = TRUE;
243 else
244 do_load = FALSE;
246 else
247 do_load = FALSE;
249 if (do_load)
251 ts.loaded = TRUE;
253 /* File open -> read contents */
254 oldname[0] = 0;
255 while (fgets (buffer, MC_MAXPATHLEN, file))
257 tree_entry *e;
258 int scanned;
259 char *lc_name;
261 /* Skip invalid records */
262 if ((buffer[0] != '0' && buffer[0] != '1'))
263 continue;
265 if (buffer[1] != ':')
266 continue;
268 scanned = buffer[0] == '1';
270 lc_name = decode (buffer + 2);
271 if (lc_name[0] != PATH_SEP)
273 /* Clear-text decompression */
274 char *s = strtok (lc_name, " ");
276 if (s)
278 common = atoi (s);
279 different = strtok (NULL, "");
280 if (different)
282 vfs_path_t *vpath;
284 vpath = vfs_path_from_str (oldname);
285 strcpy (oldname + common, different);
286 if (vfs_file_is_local (vpath))
288 vfs_path_t *tmp_vpath;
290 tmp_vpath = vfs_path_from_str (oldname);
291 e = tree_store_add_entry (tmp_vpath);
292 vfs_path_free (tmp_vpath);
293 e->scanned = scanned;
295 vfs_path_free (vpath);
299 else
301 vfs_path_t *vpath;
303 vpath = vfs_path_from_str (lc_name);
304 if (vfs_file_is_local (vpath))
306 e = tree_store_add_entry (vpath);
307 e->scanned = scanned;
309 vfs_path_free (vpath);
310 strcpy (oldname, lc_name);
312 g_free (lc_name);
314 fclose (file);
317 /* Nothing loaded, we add some standard directories */
318 if (!ts.tree_first)
320 vfs_path_t *tmp_vpath = vfs_path_from_str (PATH_SEP_STR);
321 tree_store_add_entry (tmp_vpath);
322 tree_store_rescan (tmp_vpath);
323 vfs_path_free (tmp_vpath);
324 ts.loaded = TRUE;
327 return TRUE;
330 /* --------------------------------------------------------------------------------------------- */
332 static char *
333 encode (const vfs_path_t * vpath, size_t offset)
335 char *string, *ret_val;
337 string = vfs_path_to_str (vpath);
338 ret_val = strutils_escape (string + offset, -1, "\n\\", FALSE);
339 g_free (string);
340 return ret_val;
343 /* --------------------------------------------------------------------------------------------- */
344 /** Saves the tree to the specified filename */
346 static int
347 tree_store_save_to (char *name)
349 tree_entry *current;
350 FILE *file;
352 file = fopen (name, "w");
353 if (!file)
354 return errno;
356 fprintf (file, "%s\n", TREE_SIGNATURE);
358 current = ts.tree_first;
359 while (current)
361 int i, common;
363 if (vfs_file_is_local (current->name))
365 /* Clear-text compression */
366 if (current->prev && (common = str_common (current->prev->name, current->name)) > 2)
368 char *encoded = encode (current->name, common);
370 i = fprintf (file, "%d:%d %s\n", current->scanned, common, encoded);
371 g_free (encoded);
373 else
375 char *encoded = encode (current->name, 0);
377 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
378 g_free (encoded);
381 if (i == EOF)
383 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
384 name, unix_error_string (errno));
385 break;
388 current = current->next;
390 tree_store_dirty (FALSE);
391 fclose (file);
393 return 0;
396 /* --------------------------------------------------------------------------------------------- */
398 static tree_entry *
399 tree_store_add_entry (const vfs_path_t * name)
401 int flag = -1;
402 tree_entry *current = ts.tree_first;
403 tree_entry *old = NULL;
404 tree_entry *new;
405 int submask = 0;
407 if (ts.tree_last && ts.tree_last->next)
408 abort ();
410 /* Search for the correct place */
411 while (current != NULL && (flag = pathcmp (current->name, name)) < 0)
413 old = current;
414 current = current->next;
417 if (flag == 0)
418 return current; /* Already in the list */
420 /* Not in the list -> add it */
421 new = g_new0 (tree_entry, 1);
422 if (!current)
424 /* Append to the end of the list */
425 if (!ts.tree_first)
427 /* Empty list */
428 ts.tree_first = new;
429 new->prev = NULL;
431 else
433 if (old != NULL)
434 old->next = new;
435 new->prev = old;
437 new->next = NULL;
438 ts.tree_last = new;
440 else
442 /* Insert in to the middle of the list */
443 new->prev = old;
444 if (old != NULL)
446 /* Yes, in the middle */
447 new->next = old->next;
448 old->next = new;
450 else
452 /* Nope, in the beginning of the list */
453 new->next = ts.tree_first;
454 ts.tree_first = new;
456 new->next->prev = new;
459 /* Calculate attributes */
460 new->name = vfs_path_clone (name);
461 new->sublevel = vfs_path_tokens_count (new->name);
463 const char *new_name;
465 new_name = vfs_path_get_last_path_str (new->name);
466 new->subname = strrchr (new_name, '/');
467 if (new->subname == NULL)
468 new->subname = new_name;
469 else
470 new->subname++;
472 if (new->next)
473 submask = new->next->submask;
474 else
475 submask = 0;
476 submask |= 1 << new->sublevel;
477 submask &= (2 << new->sublevel) - 1;
478 new->submask = submask;
479 new->mark = 0;
481 /* Correct the submasks of the previous entries */
482 current = new->prev;
483 while (current && current->sublevel > new->sublevel)
485 current->submask |= 1 << new->sublevel;
486 current = current->prev;
489 tree_store_dirty (TRUE);
490 return new;
493 /* --------------------------------------------------------------------------------------------- */
495 static void
496 tree_store_notify_remove (tree_entry * entry)
498 hook_t *p = remove_entry_hooks;
499 tree_store_remove_fn r;
501 while (p != NULL)
503 r = (tree_store_remove_fn) p->hook_fn;
504 r (entry, p->hook_data);
505 p = p->next;
509 /* --------------------------------------------------------------------------------------------- */
511 static tree_entry *
512 remove_entry (tree_entry * entry)
514 tree_entry *current = entry->prev;
515 long submask = 0;
516 tree_entry *ret = NULL;
518 tree_store_notify_remove (entry);
520 /* Correct the submasks of the previous entries */
521 if (entry->next)
522 submask = entry->next->submask;
523 while (current && current->sublevel > entry->sublevel)
525 submask |= 1 << current->sublevel;
526 submask &= (2 << current->sublevel) - 1;
527 current->submask = submask;
528 current = current->prev;
531 /* Unlink the entry from the list */
532 if (entry->prev)
533 entry->prev->next = entry->next;
534 else
535 ts.tree_first = entry->next;
537 if (entry->next)
538 entry->next->prev = entry->prev;
539 else
540 ts.tree_last = entry->prev;
542 /* Free the memory used by the entry */
543 g_free (entry->name);
544 g_free (entry);
546 return ret;
549 /* --------------------------------------------------------------------------------------------- */
551 static void
552 process_special_dirs (GList ** special_dirs, char *file)
554 gchar **buffers, **start_buff;
555 mc_config_t *cfg;
556 gsize buffers_len;
558 cfg = mc_config_init (file, TRUE);
559 if (cfg == NULL)
560 return;
562 start_buff = buffers = mc_config_get_string_list (cfg, "Special dirs", "list", &buffers_len);
563 if (buffers != NULL)
565 while (*buffers != NULL)
567 *special_dirs = g_list_prepend (*special_dirs, *buffers);
568 *buffers = NULL;
569 buffers++;
571 g_strfreev (start_buff);
573 mc_config_deinit (cfg);
576 /* --------------------------------------------------------------------------------------------- */
578 static gboolean
579 should_skip_directory (const vfs_path_t * vpath)
581 static GList *special_dirs = NULL;
582 GList *l;
583 static gboolean loaded = FALSE;
584 char *dir;
585 gboolean ret = FALSE;
587 if (!loaded)
589 loaded = TRUE;
590 setup_init ();
591 process_special_dirs (&special_dirs, profile_name);
592 process_special_dirs (&special_dirs, global_profile_name);
595 dir = vfs_path_to_str (vpath);
596 for (l = special_dirs; l != NULL; l = g_list_next (l))
597 if (strncmp (dir, l->data, strlen (l->data)) == 0)
599 ret = TRUE;
600 break;
603 g_free (dir);
604 return ret;
607 /* --------------------------------------------------------------------------------------------- */
608 /*** public functions ****************************************************************************/
609 /* --------------------------------------------------------------------------------------------- */
611 /* Searches for specified directory */
612 tree_entry *
613 tree_store_whereis (const vfs_path_t * name)
615 tree_entry *current = ts.tree_first;
616 int flag = -1;
618 while (current && (flag = pathcmp (current->name, name)) < 0)
619 current = current->next;
621 if (flag == 0)
622 return current;
623 else
624 return NULL;
627 /* --------------------------------------------------------------------------------------------- */
629 struct TreeStore *
630 tree_store_get (void)
632 return &ts;
635 /* --------------------------------------------------------------------------------------------- */
637 * \fn int tree_store_load(void)
638 * \brief Loads the tree from the default location
639 * \return 1 if success (true), 0 otherwise (false)
643 tree_store_load (void)
645 char *name;
646 int retval;
648 name = mc_config_get_full_path (MC_TREESTORE_FILE);
649 retval = tree_store_load_from (name);
650 g_free (name);
652 return retval;
655 /* --------------------------------------------------------------------------------------------- */
657 * \fn int tree_store_save(void)
658 * \brief Saves the tree to the default file in an atomic fashion
659 * \return 0 if success, errno on error
663 tree_store_save (void)
665 char *name;
666 int retval;
668 name = mc_config_get_full_path (MC_TREESTORE_FILE);
669 mc_util_make_backup_if_possible (name, ".tmp");
671 retval = tree_store_save_to (name);
672 if (retval != 0)
674 mc_util_restore_from_backup_if_possible (name, ".tmp");
675 g_free (name);
676 return retval;
679 mc_util_unlink_backup_if_possible (name, ".tmp");
680 g_free (name);
681 return 0;
684 /* --------------------------------------------------------------------------------------------- */
686 void
687 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
689 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
692 /* --------------------------------------------------------------------------------------------- */
694 void
695 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
697 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
701 /* --------------------------------------------------------------------------------------------- */
703 void
704 tree_store_remove_entry (const vfs_path_t * name_vpath)
706 tree_entry *current, *base, *old;
707 size_t len;
709 g_return_if_fail (name_vpath != NULL);
711 /* Miguel Ugly hack */
713 char *name;
714 gboolean is_root;
716 name = vfs_path_to_str (name_vpath);
717 is_root = (name[0] == PATH_SEP && name[1] == '\0');
718 g_free (name);
719 if (is_root)
720 return;
722 /* Miguel Ugly hack end */
724 base = tree_store_whereis (name_vpath);
725 if (!base)
726 return; /* Doesn't exist */
728 len = vfs_path_len (base->name);
729 current = base->next;
730 while (current != NULL && vfs_path_ncmp (current->name, base->name, len) == 0)
732 char *current_name;
733 gboolean ok;
735 current_name = vfs_path_to_str (current->name);
736 ok = (current_name[len] == '\0' || current_name[len] == PATH_SEP);
737 g_free (current_name);
738 if (!ok)
739 break;
740 old = current;
741 current = current->next;
742 remove_entry (old);
744 remove_entry (base);
745 tree_store_dirty (TRUE);
748 /* --------------------------------------------------------------------------------------------- */
749 /** This subdirectory exists -> clear deletion mark */
751 void
752 tree_store_mark_checked (const char *subname)
754 vfs_path_t *name;
755 char *check_name;
756 tree_entry *current, *base;
757 int flag = 1;
758 if (!ts.loaded)
759 return;
761 if (ts.check_name == NULL)
762 return;
764 /* Calculate the full name of the subdirectory */
765 if (subname[0] == '.' && (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
766 return;
768 check_name = vfs_path_to_str (ts.check_name);
769 if (check_name[0] == PATH_SEP && check_name[1] == 0)
770 name = vfs_path_build_filename (PATH_SEP_STR, subname, NULL);
771 else
772 name = vfs_path_append_new (ts.check_name, subname, NULL);
773 g_free (check_name);
775 /* Search for the subdirectory */
776 current = ts.check_start;
777 while (current && (flag = pathcmp (current->name, name)) < 0)
778 current = current->next;
780 if (flag != 0)
782 /* Doesn't exist -> add it */
783 current = tree_store_add_entry (name);
784 ts.add_queue_vpath = g_list_prepend (ts.add_queue_vpath, vfs_path_clone (name));
786 g_free (name);
788 /* Clear the deletion mark from the subdirectory and its children */
789 base = current;
790 if (base)
792 size_t len;
794 len = vfs_path_len (base->name);
795 base->mark = 0;
796 current = base->next;
797 while (current != NULL && vfs_path_ncmp (current->name, base->name, len) == 0)
799 gboolean ok;
801 check_name = vfs_path_to_str (current->name);
802 ok = (check_name[len] == '\0' || check_name[len] == PATH_SEP || len == 1);
803 g_free (check_name);
804 if (!ok)
805 break;
806 current->mark = 0;
807 current = current->next;
812 /* --------------------------------------------------------------------------------------------- */
813 /** Mark the subdirectories of the current directory for delete */
815 tree_entry *
816 tree_store_start_check (const vfs_path_t * vpath)
818 tree_entry *current, *retval;
819 size_t len;
821 if (!ts.loaded)
822 return NULL;
824 g_return_val_if_fail (ts.check_name == NULL, NULL);
825 ts.check_start = NULL;
827 /* Search for the start of subdirectories */
828 current = tree_store_whereis (vpath);
829 if (!current)
831 struct stat s;
833 if (mc_stat (vpath, &s) == -1)
834 return NULL;
836 if (!S_ISDIR (s.st_mode))
837 return NULL;
839 current = tree_store_add_entry (vpath);
840 ts.check_name = vfs_path_clone (vpath);
842 return current;
845 ts.check_name = vfs_path_clone (vpath);
847 retval = current;
849 /* Mark old subdirectories for delete */
850 ts.check_start = current->next;
851 len = vfs_path_len (ts.check_name);
853 current = ts.check_start;
854 while (current != NULL && vfs_path_ncmp (current->name, ts.check_name, len) == 0)
856 char *current_name;
857 gboolean ok;
859 current_name = vfs_path_to_str (current->name);
860 ok = (current_name[len] == '\0' || current_name[len] == PATH_SEP || len == 1);
861 g_free (current_name);
862 if (!ok)
863 break;
864 current->mark = 1;
865 current = current->next;
868 return retval;
871 /* --------------------------------------------------------------------------------------------- */
872 /** Delete subdirectories which still have the deletion mark */
874 void
875 tree_store_end_check (void)
877 tree_entry *current, *old;
878 size_t len;
879 GList *the_queue;
881 if (!ts.loaded)
882 return;
884 g_return_if_fail (ts.check_name != NULL);
886 /* Check delete marks and delete if found */
887 len = vfs_path_len (ts.check_name);
889 current = ts.check_start;
890 while (current != NULL && vfs_path_ncmp (current->name, ts.check_name, len) == 0)
892 char *current_name;
893 gboolean ok;
895 current_name = vfs_path_to_str (current->name);
896 ok = (current_name[len] == '\0' || current_name[len] == PATH_SEP || len == 1);
897 g_free (current_name);
899 if (!ok)
900 break;
901 old = current;
902 current = current->next;
903 if (old->mark)
904 remove_entry (old);
907 /* get the stuff in the scan order */
908 ts.add_queue_vpath = g_list_reverse (ts.add_queue_vpath);
909 the_queue = ts.add_queue_vpath;
910 ts.add_queue_vpath = NULL;
911 vfs_path_free (ts.check_name);
912 ts.check_name = NULL;
914 g_list_foreach (the_queue, (GFunc) vfs_path_free, NULL);
915 g_list_free (the_queue);
918 /* --------------------------------------------------------------------------------------------- */
920 tree_entry *
921 tree_store_rescan (const vfs_path_t * vpath)
923 DIR *dirp;
924 struct dirent *dp;
925 struct stat buf;
926 tree_entry *entry;
928 if (should_skip_directory (vpath))
930 entry = tree_store_add_entry (vpath);
931 entry->scanned = 1;
932 return entry;
935 entry = tree_store_start_check (vpath);
936 if (entry == NULL)
937 return NULL;
939 dirp = mc_opendir (vpath);
940 if (dirp)
942 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
944 vfs_path_t *tmp_vpath;
946 if (dp->d_name[0] == '.')
948 if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
949 continue;
952 tmp_vpath = vfs_path_append_new (vpath, dp->d_name, NULL);
953 if (mc_lstat (tmp_vpath, &buf) != -1)
955 if (S_ISDIR (buf.st_mode))
956 tree_store_mark_checked (dp->d_name);
958 vfs_path_free (tmp_vpath);
960 mc_closedir (dirp);
962 tree_store_end_check ();
963 entry->scanned = 1;
965 return entry;
968 /* --------------------------------------------------------------------------------------------- */