Ticket #2384: allow rebind Fx keys in the file manager.
[midnight-commander.git] / src / filemanager / treestore.c
blob70981080f2b02895893d29a3a16a77d881687e94
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 && (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 old->next = new;
434 new->prev = old;
436 new->next = NULL;
437 ts.tree_last = new;
439 else
441 /* Insert in to the middle of the list */
442 new->prev = old;
443 if (old)
445 /* Yes, in the middle */
446 new->next = old->next;
447 old->next = new;
449 else
451 /* Nope, in the beginning of the list */
452 new->next = ts.tree_first;
453 ts.tree_first = new;
455 new->next->prev = new;
458 /* Calculate attributes */
459 new->name = vfs_path_clone (name);
460 new->sublevel = vfs_path_tokens_count (new->name) + 1;
462 const char *new_name;
464 new_name = vfs_path_get_last_path_str (new->name);
465 new->subname = strrchr (new_name, '/');
466 if (new->subname == NULL)
467 new->subname = new_name;
469 if (new->next)
470 submask = new->next->submask;
471 else
472 submask = 0;
473 submask |= 1 << new->sublevel;
474 submask &= (2 << new->sublevel) - 1;
475 new->submask = submask;
476 new->mark = 0;
478 /* Correct the submasks of the previous entries */
479 current = new->prev;
480 while (current && current->sublevel > new->sublevel)
482 current->submask |= 1 << new->sublevel;
483 current = current->prev;
486 /* The entry has now been added */
488 if (new->sublevel > 1)
490 /* Let's check if the parent directory is in the tree */
491 vfs_path_t *tmp_vpath;
493 tmp_vpath = vfs_path_vtokens_get (new->name, 0, new->sublevel - 1);
494 tree_store_add_entry (tmp_vpath);
495 vfs_path_free (tmp_vpath);
498 tree_store_dirty (TRUE);
499 return new;
502 /* --------------------------------------------------------------------------------------------- */
504 static void
505 tree_store_notify_remove (tree_entry * entry)
507 hook_t *p = remove_entry_hooks;
508 tree_store_remove_fn r;
510 while (p != NULL)
512 r = (tree_store_remove_fn) p->hook_fn;
513 r (entry, p->hook_data);
514 p = p->next;
518 /* --------------------------------------------------------------------------------------------- */
520 static tree_entry *
521 remove_entry (tree_entry * entry)
523 tree_entry *current = entry->prev;
524 long submask = 0;
525 tree_entry *ret = NULL;
527 tree_store_notify_remove (entry);
529 /* Correct the submasks of the previous entries */
530 if (entry->next)
531 submask = entry->next->submask;
532 while (current && current->sublevel > entry->sublevel)
534 submask |= 1 << current->sublevel;
535 submask &= (2 << current->sublevel) - 1;
536 current->submask = submask;
537 current = current->prev;
540 /* Unlink the entry from the list */
541 if (entry->prev)
542 entry->prev->next = entry->next;
543 else
544 ts.tree_first = entry->next;
546 if (entry->next)
547 entry->next->prev = entry->prev;
548 else
549 ts.tree_last = entry->prev;
551 /* Free the memory used by the entry */
552 g_free (entry->name);
553 g_free (entry);
555 return ret;
558 /* --------------------------------------------------------------------------------------------- */
560 static void
561 process_special_dirs (GList ** special_dirs, char *file)
563 gchar **buffers, **start_buff;
564 mc_config_t *cfg;
565 gsize buffers_len;
567 cfg = mc_config_init (file);
568 if (cfg == NULL)
569 return;
571 start_buff = buffers = mc_config_get_string_list (cfg, "Special dirs", "list", &buffers_len);
572 if (buffers != NULL)
574 while (*buffers != NULL)
576 *special_dirs = g_list_prepend (*special_dirs, *buffers);
577 *buffers = NULL;
578 buffers++;
580 g_strfreev (start_buff);
582 mc_config_deinit (cfg);
585 /* --------------------------------------------------------------------------------------------- */
587 static gboolean
588 should_skip_directory (const vfs_path_t * vpath)
590 static GList *special_dirs = NULL;
591 GList *l;
592 static gboolean loaded = FALSE;
593 char *dir;
594 gboolean ret = FALSE;
596 if (!loaded)
598 loaded = TRUE;
599 setup_init ();
600 process_special_dirs (&special_dirs, profile_name);
601 process_special_dirs (&special_dirs, global_profile_name);
604 dir = vfs_path_to_str (vpath);
605 for (l = special_dirs; l != NULL; l = g_list_next (l))
606 if (strncmp (dir, l->data, strlen (l->data)) == 0)
608 ret = TRUE;
609 break;
612 g_free (dir);
613 return ret;
616 /* --------------------------------------------------------------------------------------------- */
617 /*** public functions ****************************************************************************/
618 /* --------------------------------------------------------------------------------------------- */
620 /* Searches for specified directory */
621 tree_entry *
622 tree_store_whereis (const vfs_path_t * name)
624 tree_entry *current = ts.tree_first;
625 int flag = -1;
627 while (current && (flag = pathcmp (current->name, name)) < 0)
628 current = current->next;
630 if (flag == 0)
631 return current;
632 else
633 return NULL;
636 /* --------------------------------------------------------------------------------------------- */
638 struct TreeStore *
639 tree_store_get (void)
641 return &ts;
644 /* --------------------------------------------------------------------------------------------- */
646 * \fn int tree_store_load(void)
647 * \brief Loads the tree from the default location
648 * \return 1 if success (true), 0 otherwise (false)
652 tree_store_load (void)
654 char *name;
655 int retval;
657 name = mc_config_get_full_path (MC_TREESTORE_FILE);
658 retval = tree_store_load_from (name);
659 g_free (name);
661 return retval;
664 /* --------------------------------------------------------------------------------------------- */
666 * \fn int tree_store_save(void)
667 * \brief Saves the tree to the default file in an atomic fashion
668 * \return 0 if success, errno on error
672 tree_store_save (void)
674 char *name;
675 int retval;
677 name = mc_config_get_full_path (MC_TREESTORE_FILE);
678 mc_util_make_backup_if_possible (name, ".tmp");
680 retval = tree_store_save_to (name);
681 if (retval != 0)
683 mc_util_restore_from_backup_if_possible (name, ".tmp");
684 g_free (name);
685 return retval;
688 mc_util_unlink_backup_if_possible (name, ".tmp");
689 g_free (name);
690 return 0;
693 /* --------------------------------------------------------------------------------------------- */
695 void
696 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
698 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
701 /* --------------------------------------------------------------------------------------------- */
703 void
704 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
706 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
710 /* --------------------------------------------------------------------------------------------- */
712 void
713 tree_store_remove_entry (const vfs_path_t * name_vpath)
715 tree_entry *current, *base, *old;
716 size_t len;
718 g_return_if_fail (name_vpath != NULL);
720 /* Miguel Ugly hack */
722 char *name;
723 gboolean is_root;
725 name = vfs_path_to_str (name_vpath);
726 is_root = (name[0] == PATH_SEP && name[1] == '\0');
727 g_free (name);
728 if (is_root)
729 return;
731 /* Miguel Ugly hack end */
733 base = tree_store_whereis (name_vpath);
734 if (!base)
735 return; /* Doesn't exist */
737 len = vfs_path_len (base->name);
738 current = base->next;
739 while (current != NULL && vfs_path_ncmp (current->name, base->name, len) == 0)
741 char *current_name;
742 gboolean ok;
744 current_name = vfs_path_to_str (current->name);
745 ok = (current_name[len] == '\0' || current_name[len] == PATH_SEP);
746 g_free (current_name);
747 if (!ok)
748 break;
749 old = current;
750 current = current->next;
751 remove_entry (old);
753 remove_entry (base);
754 tree_store_dirty (TRUE);
757 /* --------------------------------------------------------------------------------------------- */
758 /** This subdirectory exists -> clear deletion mark */
760 void
761 tree_store_mark_checked (const char *subname)
763 vfs_path_t *name;
764 char *check_name;
765 tree_entry *current, *base;
766 int flag = 1;
767 if (!ts.loaded)
768 return;
770 if (ts.check_name == NULL)
771 return;
773 /* Calculate the full name of the subdirectory */
774 if (subname[0] == '.' && (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
775 return;
777 check_name = vfs_path_to_str (ts.check_name);
778 if (check_name[0] == PATH_SEP && check_name[1] == 0)
779 name = vfs_path_build_filename (PATH_SEP_STR, subname, NULL);
780 else
781 name = vfs_path_append_new (ts.check_name, subname, NULL);
782 g_free (check_name);
784 /* Search for the subdirectory */
785 current = ts.check_start;
786 while (current && (flag = pathcmp (current->name, name)) < 0)
787 current = current->next;
789 if (flag != 0)
791 /* Doesn't exist -> add it */
792 current = tree_store_add_entry (name);
793 ts.add_queue_vpath = g_list_prepend (ts.add_queue_vpath, vfs_path_clone (name));
795 g_free (name);
797 /* Clear the deletion mark from the subdirectory and its children */
798 base = current;
799 if (base)
801 size_t len;
803 len = vfs_path_len (base->name);
804 base->mark = 0;
805 current = base->next;
806 while (current != NULL && vfs_path_ncmp (current->name, base->name, len) == 0)
808 gboolean ok;
810 check_name = vfs_path_to_str (current->name);
811 ok = (check_name[len] == '\0' || check_name[len] == PATH_SEP || len == 1);
812 g_free (check_name);
813 if (!ok)
814 break;
815 current->mark = 0;
816 current = current->next;
821 /* --------------------------------------------------------------------------------------------- */
822 /** Mark the subdirectories of the current directory for delete */
824 tree_entry *
825 tree_store_start_check (const vfs_path_t * vpath)
827 tree_entry *current, *retval;
828 size_t len;
830 if (!ts.loaded)
831 return NULL;
833 g_return_val_if_fail (ts.check_name == NULL, NULL);
834 ts.check_start = NULL;
836 /* Search for the start of subdirectories */
837 current = tree_store_whereis (vpath);
838 if (!current)
840 struct stat s;
842 if (mc_stat (vpath, &s) == -1)
843 return NULL;
845 if (!S_ISDIR (s.st_mode))
846 return NULL;
848 current = tree_store_add_entry (vpath);
849 ts.check_name = vfs_path_clone (vpath);
851 return current;
854 ts.check_name = vfs_path_clone (vpath);
856 retval = current;
858 /* Mark old subdirectories for delete */
859 ts.check_start = current->next;
860 len = vfs_path_len (ts.check_name);
862 current = ts.check_start;
863 while (current != NULL && vfs_path_cmp (current->name, ts.check_name) == 0)
865 char *current_name;
866 gboolean ok;
868 current_name = vfs_path_to_str (current->name);
869 ok = (current_name[len] == '\0' || (current_name[len] == PATH_SEP || len == 1));
870 g_free (current_name);
871 if (!ok)
872 break;
873 current->mark = 1;
874 current = current->next;
877 return retval;
880 /* --------------------------------------------------------------------------------------------- */
881 /** Delete subdirectories which still have the deletion mark */
883 void
884 tree_store_end_check (void)
886 tree_entry *current, *old;
887 size_t len;
888 GList *the_queue;
890 if (!ts.loaded)
891 return;
893 g_return_if_fail (ts.check_name != NULL);
895 /* Check delete marks and delete if found */
896 len = vfs_path_len (ts.check_name);
898 current = ts.check_start;
899 while (current != NULL && vfs_path_ncmp (current->name, ts.check_name, len) == 0)
901 char *current_name;
902 gboolean ok;
904 current_name = vfs_path_to_str (current->name);
905 ok = (current_name[len] == '\0' || current_name[len] == PATH_SEP || len == 1);
906 g_free (current_name);
908 if (!ok)
909 break;
910 old = current;
911 current = current->next;
912 if (old->mark)
913 remove_entry (old);
916 /* get the stuff in the scan order */
917 ts.add_queue_vpath = g_list_reverse (ts.add_queue_vpath);
918 the_queue = ts.add_queue_vpath;
919 ts.add_queue_vpath = NULL;
920 g_free (ts.check_name);
921 ts.check_name = NULL;
923 g_list_foreach (the_queue, (GFunc) vfs_path_free, NULL);
924 g_list_free (the_queue);
927 /* --------------------------------------------------------------------------------------------- */
929 tree_entry *
930 tree_store_rescan (const vfs_path_t * vpath)
932 DIR *dirp;
933 struct dirent *dp;
934 struct stat buf;
935 tree_entry *entry;
937 if (should_skip_directory (vpath))
939 entry = tree_store_add_entry (vpath);
940 entry->scanned = 1;
941 return entry;
944 entry = tree_store_start_check (vpath);
945 if (entry == NULL)
946 return NULL;
948 dirp = mc_opendir (vpath);
949 if (dirp)
951 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
953 vfs_path_t *tmp_vpath;
955 if (dp->d_name[0] == '.')
957 if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
958 continue;
961 tmp_vpath = vfs_path_append_new (vpath, dp->d_name, NULL);
962 if (mc_lstat (tmp_vpath, &buf) != -1)
964 if (S_ISDIR (buf.st_mode))
965 tree_store_mark_checked (dp->d_name);
967 vfs_path_free (tmp_vpath);
969 mc_closedir (dirp);
971 tree_store_end_check ();
972 entry->scanned = 1;
974 return entry;
977 /* --------------------------------------------------------------------------------------------- */