Split file src/keybind.[ch] to lib/keybind.[ch] and src/keybind-defaults.[ch].
[midnight-commander.git] / src / treestore.c
blobb778d345cd31db18bdf1a2dd53340b747e6adfbb
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"
54 #include "lib/hook.h"
55 #include "lib/util.h"
57 #include "treestore.h"
58 #include "setup.h"
60 /*** global variables ****************************************************************************/
62 /*** file scope macro definitions ****************************************************************/
64 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
66 /*** file scope type declarations ****************************************************************/
68 /*** file scope variables ************************************************************************/
70 static struct TreeStore ts;
72 static hook_t *remove_entry_hooks;
74 /*** file scope functions ************************************************************************/
75 /* --------------------------------------------------------------------------------------------- */
77 static tree_entry *tree_store_add_entry (const char *name);
79 /* --------------------------------------------------------------------------------------------- */
81 static void
82 tree_store_dirty (int state)
84 ts.dirty = state;
87 /* --------------------------------------------------------------------------------------------- */
88 /** Returns the number of common bytes in the strings. */
90 static size_t
91 str_common (const char *s1, const char *s2)
93 size_t result = 0;
95 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
96 result++;
97 return result;
100 /* --------------------------------------------------------------------------------------------- */
101 /* The directory names are arranged in a single linked list in the same
102 order as they are displayed. When the tree is displayed the expected
103 order is like this:
105 /bin
106 /etc
107 /etc/X11
108 /etc/rc.d
109 /etc.old/X11
110 /etc.old/rc.d
111 /usr
113 i.e. the required collating sequence when comparing two directory names is
114 '\0' < PATH_SEP < all-other-characters-in-encoding-order
116 Since strcmp doesn't fulfil this requirement we use pathcmp when
117 inserting directory names into the list. The meaning of the return value
118 of pathcmp and strcmp are the same (an integer less than, equal to, or
119 greater than zero if p1 is found to be less than, to match, or be greater
120 than p2.
123 static int
124 pathcmp (const char *p1, const char *p2)
126 for (; *p1 == *p2; p1++, p2++)
127 if (*p1 == '\0')
128 return 0;
130 if (*p1 == '\0')
131 return -1;
132 if (*p2 == '\0')
133 return 1;
134 if (*p1 == PATH_SEP)
135 return -1;
136 if (*p2 == PATH_SEP)
137 return 1;
138 return (*p1 - *p2);
141 /* --------------------------------------------------------------------------------------------- */
143 static char *
144 decode (char *buffer)
146 char *res = g_strdup (buffer);
147 char *p, *q;
149 for (p = q = res; *p; p++, q++)
151 if (*p == '\n')
153 *q = 0;
154 return res;
157 if (*p != '\\')
159 *q = *p;
160 continue;
163 p++;
165 switch (*p)
167 case 'n':
168 *q = '\n';
169 break;
170 case '\\':
171 *q = '\\';
172 break;
175 *q = *p;
177 return res;
180 /* --------------------------------------------------------------------------------------------- */
181 /** Loads the tree store from the specified filename */
183 static int
184 tree_store_load_from (char *name)
186 FILE *file;
187 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
188 char *different;
189 int len, common;
190 int do_load;
192 g_return_val_if_fail (name != NULL, FALSE);
194 if (ts.loaded)
195 return TRUE;
197 file = fopen (name, "r");
199 if (file)
201 if (fgets (buffer, sizeof (buffer), file) != NULL)
203 if (strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0)
205 fclose (file);
206 do_load = FALSE;
208 else
209 do_load = TRUE;
211 else
212 do_load = FALSE;
214 else
215 do_load = FALSE;
217 if (do_load)
219 ts.loaded = TRUE;
221 /* File open -> read contents */
222 oldname[0] = 0;
223 while (fgets (buffer, MC_MAXPATHLEN, file))
225 tree_entry *e;
226 int scanned;
227 char *lc_name;
229 /* Skip invalid records */
230 if ((buffer[0] != '0' && buffer[0] != '1'))
231 continue;
233 if (buffer[1] != ':')
234 continue;
236 scanned = buffer[0] == '1';
238 lc_name = decode (buffer + 2);
240 len = strlen (lc_name);
241 if (lc_name[0] != PATH_SEP)
243 /* Clear-text decompression */
244 char *s = strtok (lc_name, " ");
246 if (s)
248 common = atoi (s);
249 different = strtok (NULL, "");
250 if (different)
252 strcpy (oldname + common, different);
253 if (vfs_file_is_local (oldname))
255 e = tree_store_add_entry (oldname);
256 e->scanned = scanned;
261 else
263 if (vfs_file_is_local (lc_name))
265 e = tree_store_add_entry (lc_name);
266 e->scanned = scanned;
268 strcpy (oldname, lc_name);
270 g_free (lc_name);
272 fclose (file);
275 /* Nothing loaded, we add some standard directories */
276 if (!ts.tree_first)
278 tree_store_add_entry (PATH_SEP_STR);
279 tree_store_rescan (PATH_SEP_STR);
280 ts.loaded = TRUE;
283 return TRUE;
286 /* --------------------------------------------------------------------------------------------- */
288 static char *
289 encode (const char *string)
291 int special_chars;
292 const char *p;
293 char *q;
294 char *res;
296 for (special_chars = 0, p = string; *p; p++)
298 if (*p == '\n' || *p == '\\')
299 special_chars++;
302 res = g_malloc (p - string + special_chars + 1);
303 for (p = string, q = res; *p; p++, q++)
305 if (*p != '\n' && *p != '\\')
307 *q = *p;
308 continue;
311 *q++ = '\\';
313 switch (*p)
315 case '\n':
316 *q = 'n';
317 break;
319 case '\\':
320 *q = '\\';
321 break;
324 *q = 0;
325 return res;
328 /* --------------------------------------------------------------------------------------------- */
329 /** Saves the tree to the specified filename */
331 static int
332 tree_store_save_to (char *name)
334 tree_entry *current;
335 FILE *file;
337 file = fopen (name, "w");
338 if (!file)
339 return errno;
341 fprintf (file, "%s\n", TREE_SIGNATURE);
343 current = ts.tree_first;
344 while (current)
346 int i, common;
348 if (vfs_file_is_local (current->name))
350 /* Clear-text compression */
351 if (current->prev && (common = str_common (current->prev->name, current->name)) > 2)
353 char *encoded = encode (current->name + common);
355 i = fprintf (file, "%d:%d %s\n", current->scanned, common, encoded);
356 g_free (encoded);
358 else
360 char *encoded = encode (current->name);
362 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
363 g_free (encoded);
366 if (i == EOF)
368 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
369 name, unix_error_string (errno));
370 break;
373 current = current->next;
375 tree_store_dirty (FALSE);
376 fclose (file);
378 return 0;
381 /* --------------------------------------------------------------------------------------------- */
383 static tree_entry *
384 tree_store_add_entry (const char *name)
386 int flag = -1;
387 tree_entry *current = ts.tree_first;
388 tree_entry *old = NULL;
389 tree_entry *new;
390 int i, len;
391 int submask = 0;
393 if (ts.tree_last && ts.tree_last->next)
394 abort ();
396 /* Search for the correct place */
397 while (current && (flag = pathcmp (current->name, name)) < 0)
399 old = current;
400 current = current->next;
403 if (flag == 0)
404 return current; /* Already in the list */
406 /* Not in the list -> add it */
407 new = g_new0 (tree_entry, 1);
408 if (!current)
410 /* Append to the end of the list */
411 if (!ts.tree_first)
413 /* Empty list */
414 ts.tree_first = new;
415 new->prev = NULL;
417 else
419 old->next = new;
420 new->prev = old;
422 new->next = NULL;
423 ts.tree_last = new;
425 else
427 /* Insert in to the middle of the list */
428 new->prev = old;
429 if (old)
431 /* Yes, in the middle */
432 new->next = old->next;
433 old->next = new;
435 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)
451 new->sublevel++;
452 new->subname = new->name + i + 1;
454 if (new->next)
455 submask = new->next->submask;
456 else
457 submask = 0;
458 submask |= 1 << new->sublevel;
459 submask &= (2 << new->sublevel) - 1;
460 new->submask = submask;
461 new->mark = 0;
463 /* Correct the submasks of the previous entries */
464 current = new->prev;
465 while (current && current->sublevel > new->sublevel)
467 current->submask |= 1 << new->sublevel;
468 current = current->prev;
471 /* The entry has now been added */
473 if (new->sublevel > 1)
475 /* Let's check if the parent directory is in the tree */
476 char *parent = g_strdup (new->name);
478 for (i = strlen (parent) - 1; i > 1; i--)
480 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 /* --------------------------------------------------------------------------------------------- */
496 static void
497 tree_store_notify_remove (tree_entry * entry)
499 hook_t *p = remove_entry_hooks;
500 tree_store_remove_fn r;
502 while (p != NULL)
504 r = (tree_store_remove_fn) p->hook_fn;
505 r (entry, p->hook_data);
506 p = p->next;
510 /* --------------------------------------------------------------------------------------------- */
512 static tree_entry *
513 remove_entry (tree_entry * entry)
515 tree_entry *current = entry->prev;
516 long submask = 0;
517 tree_entry *ret = NULL;
519 tree_store_notify_remove (entry);
521 /* Correct the submasks of the previous entries */
522 if (entry->next)
523 submask = entry->next->submask;
524 while (current && current->sublevel > entry->sublevel)
526 submask |= 1 << current->sublevel;
527 submask &= (2 << current->sublevel) - 1;
528 current->submask = submask;
529 current = current->prev;
532 /* Unlink the entry from the list */
533 if (entry->prev)
534 entry->prev->next = entry->next;
535 else
536 ts.tree_first = entry->next;
538 if (entry->next)
539 entry->next->prev = entry->prev;
540 else
541 ts.tree_last = entry->prev;
543 /* Free the memory used by the entry */
544 g_free (entry->name);
545 g_free (entry);
547 return ret;
550 /* --------------------------------------------------------------------------------------------- */
552 static void
553 process_special_dirs (GList ** special_dirs, char *file)
555 gchar **buffers, **start_buff;
556 mc_config_t *cfg;
557 gsize buffers_len;
559 cfg = mc_config_init (file);
560 if (cfg == NULL)
561 return;
563 start_buff = buffers = mc_config_get_string_list (cfg, "Special dirs", "list", &buffers_len);
564 if (buffers != NULL)
566 while (*buffers != NULL)
568 *special_dirs = g_list_prepend (*special_dirs, *buffers);
569 *buffers = NULL;
570 buffers++;
572 g_strfreev (start_buff);
574 mc_config_deinit (cfg);
577 /* --------------------------------------------------------------------------------------------- */
579 static gboolean
580 should_skip_directory (const char *dir)
582 static GList *special_dirs = NULL;
583 GList *l;
584 static gboolean loaded = FALSE;
586 if (!loaded)
588 loaded = TRUE;
589 setup_init ();
590 process_special_dirs (&special_dirs, profile_name);
591 process_special_dirs (&special_dirs, global_profile_name);
594 for (l = special_dirs; l != NULL; l = g_list_next (l))
595 if (strncmp (dir, l->data, strlen (l->data)) == 0)
596 return TRUE;
598 return FALSE;
601 /* --------------------------------------------------------------------------------------------- */
602 /*** public functions ****************************************************************************/
603 /* --------------------------------------------------------------------------------------------- */
605 /* Searches for specified directory */
606 tree_entry *
607 tree_store_whereis (const char *name)
609 tree_entry *current = ts.tree_first;
610 int flag = -1;
612 while (current && (flag = pathcmp (current->name, name)) < 0)
613 current = current->next;
615 if (flag == 0)
616 return current;
617 else
618 return NULL;
621 /* --------------------------------------------------------------------------------------------- */
623 struct TreeStore *
624 tree_store_get (void)
626 return &ts;
629 /* --------------------------------------------------------------------------------------------- */
631 * \fn int tree_store_load(void)
632 * \brief Loads the tree from the default location
633 * \return 1 if success (true), 0 otherwise (false)
637 tree_store_load (void)
639 char *name;
640 int retval;
642 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
643 retval = tree_store_load_from (name);
644 g_free (name);
646 return retval;
649 /* --------------------------------------------------------------------------------------------- */
651 * \fn int tree_store_save(void)
652 * \brief Saves the tree to the default file in an atomic fashion
653 * \return 0 if success, errno on error
657 tree_store_save (void)
659 char *name;
660 int retval;
662 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
663 mc_util_make_backup_if_possible (name, ".tmp");
665 retval = tree_store_save_to (name);
666 if (retval != 0)
668 mc_util_restore_from_backup_if_possible (name, ".tmp");
669 g_free (name);
670 return retval;
673 mc_util_unlink_backup_if_possible (name, ".tmp");
674 g_free (name);
675 return 0;
678 /* --------------------------------------------------------------------------------------------- */
680 void
681 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
683 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
686 /* --------------------------------------------------------------------------------------------- */
688 void
689 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
691 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
695 /* --------------------------------------------------------------------------------------------- */
697 void
698 tree_store_remove_entry (const char *name)
700 tree_entry *current, *base, *old;
701 int len;
703 g_return_if_fail (name != NULL);
705 /* Miguel Ugly hack */
706 if (name[0] == PATH_SEP && name[1] == 0)
707 return;
708 /* Miguel Ugly hack end */
710 base = tree_store_whereis (name);
711 if (!base)
712 return; /* Doesn't exist */
714 len = strlen (base->name);
715 current = base->next;
716 while (current
717 && strncmp (current->name, base->name, len) == 0
718 && (current->name[len] == '\0' || current->name[len] == PATH_SEP))
720 old = current;
721 current = current->next;
722 remove_entry (old);
724 remove_entry (base);
725 tree_store_dirty (TRUE);
727 return;
730 /* --------------------------------------------------------------------------------------------- */
731 /** This subdirectory exists -> clear deletion mark */
733 void
734 tree_store_mark_checked (const char *subname)
736 char *name;
737 tree_entry *current, *base;
738 int flag = 1, len;
739 if (!ts.loaded)
740 return;
742 if (ts.check_name == NULL)
743 return;
745 /* Calculate the full name of the subdirectory */
746 if (subname[0] == '.' && (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
747 return;
748 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
749 name = g_strconcat (PATH_SEP_STR, subname, (char *) NULL);
750 else
751 name = concat_dir_and_file (ts.check_name, subname);
753 /* Search for the subdirectory */
754 current = ts.check_start;
755 while (current && (flag = pathcmp (current->name, name)) < 0)
756 current = current->next;
758 if (flag != 0)
760 /* Doesn't exist -> add it */
761 current = tree_store_add_entry (name);
762 ts.add_queue = g_list_prepend (ts.add_queue, g_strdup (name));
764 g_free (name);
766 /* Clear the deletion mark from the subdirectory and its children */
767 base = current;
768 if (base)
770 len = strlen (base->name);
771 base->mark = 0;
772 current = base->next;
773 while (current
774 && strncmp (current->name, base->name, len) == 0
775 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
777 current->mark = 0;
778 current = current->next;
783 /* --------------------------------------------------------------------------------------------- */
784 /** Mark the subdirectories of the current directory for delete */
786 tree_entry *
787 tree_store_start_check (const char *path)
789 tree_entry *current, *retval;
790 int len;
792 if (!ts.loaded)
793 return NULL;
795 g_return_val_if_fail (ts.check_name == NULL, NULL);
796 ts.check_start = NULL;
798 /* Search for the start of subdirectories */
799 current = tree_store_whereis (path);
800 if (!current)
802 struct stat s;
804 if (mc_stat (path, &s) == -1)
805 return NULL;
807 if (!S_ISDIR (s.st_mode))
808 return NULL;
810 current = tree_store_add_entry (path);
811 ts.check_name = g_strdup (path);
813 return current;
816 ts.check_name = g_strdup (path);
818 retval = current;
820 /* Mark old subdirectories for delete */
821 ts.check_start = current->next;
822 len = strlen (ts.check_name);
824 current = ts.check_start;
825 while (current
826 && strncmp (current->name, ts.check_name, len) == 0
827 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
829 current->mark = 1;
830 current = current->next;
833 return retval;
836 /* --------------------------------------------------------------------------------------------- */
837 /** Delete subdirectories which still have the deletion mark */
839 void
840 tree_store_end_check (void)
842 tree_entry *current, *old;
843 size_t len;
844 GList *the_queue;
846 if (!ts.loaded)
847 return;
849 g_return_if_fail (ts.check_name != NULL);
851 /* Check delete marks and delete if found */
852 len = strlen (ts.check_name);
854 current = ts.check_start;
855 while (current
856 && strncmp (current->name, ts.check_name, len) == 0
857 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
859 old = current;
860 current = current->next;
861 if (old->mark)
862 remove_entry (old);
865 /* get the stuff in the scan order */
866 ts.add_queue = g_list_reverse (ts.add_queue);
867 the_queue = ts.add_queue;
868 ts.add_queue = NULL;
869 g_free (ts.check_name);
870 ts.check_name = NULL;
872 g_list_foreach (the_queue, (GFunc) g_free, NULL);
873 g_list_free (the_queue);
876 /* --------------------------------------------------------------------------------------------- */
878 tree_entry *
879 tree_store_rescan (const char *dir)
881 DIR *dirp;
882 struct dirent *dp;
883 struct stat buf;
884 tree_entry *entry;
886 if (should_skip_directory (dir))
888 entry = tree_store_add_entry (dir);
889 entry->scanned = 1;
891 return entry;
894 entry = tree_store_start_check (dir);
896 if (!entry)
897 return NULL;
899 dirp = mc_opendir (dir);
900 if (dirp)
902 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
904 char *full_name;
906 if (dp->d_name[0] == '.')
908 if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
909 continue;
912 full_name = concat_dir_and_file (dir, dp->d_name);
913 if (mc_lstat (full_name, &buf) != -1)
915 if (S_ISDIR (buf.st_mode))
916 tree_store_mark_checked (dp->d_name);
918 g_free (full_name);
920 mc_closedir (dirp);
922 tree_store_end_check ();
923 entry->scanned = 1;
925 return entry;
928 /* --------------------------------------------------------------------------------------------- */