Fix of mouse and ca capabilities check.
[midnight-commander.git] / src / filemanager / treestore.c
blob509e455df41226d2e4b625084434f5074a54d339
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 "src/setup.h"
59 #include "treestore.h"
61 /*** global variables ****************************************************************************/
63 /*** file scope macro definitions ****************************************************************/
65 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
67 /*** file scope type declarations ****************************************************************/
69 /*** file scope variables ************************************************************************/
71 static struct TreeStore ts;
73 static hook_t *remove_entry_hooks;
75 /*** file scope functions ************************************************************************/
76 /* --------------------------------------------------------------------------------------------- */
78 static tree_entry *tree_store_add_entry (const char *name);
80 /* --------------------------------------------------------------------------------------------- */
82 static void
83 tree_store_dirty (int state)
85 ts.dirty = state;
88 /* --------------------------------------------------------------------------------------------- */
89 /** Returns the number of common bytes in the strings. */
91 static size_t
92 str_common (const char *s1, const char *s2)
94 size_t result = 0;
96 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
97 result++;
98 return result;
101 /* --------------------------------------------------------------------------------------------- */
102 /* The directory names are arranged in a single linked list in the same
103 order as they are displayed. When the tree is displayed the expected
104 order is like this:
106 /bin
107 /etc
108 /etc/X11
109 /etc/rc.d
110 /etc.old/X11
111 /etc.old/rc.d
112 /usr
114 i.e. the required collating sequence when comparing two directory names is
115 '\0' < PATH_SEP < all-other-characters-in-encoding-order
117 Since strcmp doesn't fulfil this requirement we use pathcmp when
118 inserting directory names into the list. The meaning of the return value
119 of pathcmp and strcmp are the same (an integer less than, equal to, or
120 greater than zero if p1 is found to be less than, to match, or be greater
121 than p2.
124 static int
125 pathcmp (const char *p1, const char *p2)
127 for (; *p1 == *p2; p1++, p2++)
128 if (*p1 == '\0')
129 return 0;
131 if (*p1 == '\0')
132 return -1;
133 if (*p2 == '\0')
134 return 1;
135 if (*p1 == PATH_SEP)
136 return -1;
137 if (*p2 == PATH_SEP)
138 return 1;
139 return (*p1 - *p2);
142 /* --------------------------------------------------------------------------------------------- */
144 static char *
145 decode (char *buffer)
147 char *res = g_strdup (buffer);
148 char *p, *q;
150 for (p = q = res; *p; p++, q++)
152 if (*p == '\n')
154 *q = 0;
155 return res;
158 if (*p != '\\')
160 *q = *p;
161 continue;
164 p++;
166 switch (*p)
168 case 'n':
169 *q = '\n';
170 break;
171 case '\\':
172 *q = '\\';
173 break;
176 *q = *p;
178 return res;
181 /* --------------------------------------------------------------------------------------------- */
182 /** Loads the tree store from the specified filename */
184 static int
185 tree_store_load_from (char *name)
187 FILE *file;
188 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
189 char *different;
190 int common;
191 int do_load;
193 g_return_val_if_fail (name != NULL, FALSE);
195 if (ts.loaded)
196 return TRUE;
198 file = fopen (name, "r");
200 if (file)
202 if (fgets (buffer, sizeof (buffer), file) != NULL)
204 if (strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0)
206 fclose (file);
207 do_load = FALSE;
209 else
210 do_load = TRUE;
212 else
213 do_load = FALSE;
215 else
216 do_load = FALSE;
218 if (do_load)
220 ts.loaded = TRUE;
222 /* File open -> read contents */
223 oldname[0] = 0;
224 while (fgets (buffer, MC_MAXPATHLEN, file))
226 tree_entry *e;
227 int scanned;
228 char *lc_name;
230 /* Skip invalid records */
231 if ((buffer[0] != '0' && buffer[0] != '1'))
232 continue;
234 if (buffer[1] != ':')
235 continue;
237 scanned = buffer[0] == '1';
239 lc_name = decode (buffer + 2);
240 if (lc_name[0] != PATH_SEP)
242 /* Clear-text decompression */
243 char *s = strtok (lc_name, " ");
245 if (s)
247 common = atoi (s);
248 different = strtok (NULL, "");
249 if (different)
251 strcpy (oldname + common, different);
252 if (vfs_file_is_local (oldname))
254 e = tree_store_add_entry (oldname);
255 e->scanned = scanned;
260 else
262 if (vfs_file_is_local (lc_name))
264 e = tree_store_add_entry (lc_name);
265 e->scanned = scanned;
267 strcpy (oldname, lc_name);
269 g_free (lc_name);
271 fclose (file);
274 /* Nothing loaded, we add some standard directories */
275 if (!ts.tree_first)
277 tree_store_add_entry (PATH_SEP_STR);
278 tree_store_rescan (PATH_SEP_STR);
279 ts.loaded = TRUE;
282 return TRUE;
285 /* --------------------------------------------------------------------------------------------- */
287 static char *
288 encode (const char *string)
290 int special_chars;
291 const char *p;
292 char *q;
293 char *res;
295 for (special_chars = 0, p = string; *p; p++)
297 if (*p == '\n' || *p == '\\')
298 special_chars++;
301 res = g_malloc (p - string + special_chars + 1);
302 for (p = string, q = res; *p; p++, q++)
304 if (*p != '\n' && *p != '\\')
306 *q = *p;
307 continue;
310 *q++ = '\\';
312 switch (*p)
314 case '\n':
315 *q = 'n';
316 break;
318 case '\\':
319 *q = '\\';
320 break;
323 *q = 0;
324 return res;
327 /* --------------------------------------------------------------------------------------------- */
328 /** Saves the tree to the specified filename */
330 static int
331 tree_store_save_to (char *name)
333 tree_entry *current;
334 FILE *file;
336 file = fopen (name, "w");
337 if (!file)
338 return errno;
340 fprintf (file, "%s\n", TREE_SIGNATURE);
342 current = ts.tree_first;
343 while (current)
345 int i, common;
347 if (vfs_file_is_local (current->name))
349 /* Clear-text compression */
350 if (current->prev && (common = str_common (current->prev->name, current->name)) > 2)
352 char *encoded = encode (current->name + common);
354 i = fprintf (file, "%d:%d %s\n", current->scanned, common, encoded);
355 g_free (encoded);
357 else
359 char *encoded = encode (current->name);
361 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
362 g_free (encoded);
365 if (i == EOF)
367 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
368 name, unix_error_string (errno));
369 break;
372 current = current->next;
374 tree_store_dirty (FALSE);
375 fclose (file);
377 return 0;
380 /* --------------------------------------------------------------------------------------------- */
382 static tree_entry *
383 tree_store_add_entry (const char *name)
385 int flag = -1;
386 tree_entry *current = ts.tree_first;
387 tree_entry *old = NULL;
388 tree_entry *new;
389 int i, len;
390 int submask = 0;
392 if (ts.tree_last && ts.tree_last->next)
393 abort ();
395 /* Search for the correct place */
396 while (current && (flag = pathcmp (current->name, name)) < 0)
398 old = current;
399 current = current->next;
402 if (flag == 0)
403 return current; /* Already in the list */
405 /* Not in the list -> add it */
406 new = g_new0 (tree_entry, 1);
407 if (!current)
409 /* Append to the end of the list */
410 if (!ts.tree_first)
412 /* Empty list */
413 ts.tree_first = new;
414 new->prev = NULL;
416 else
418 old->next = new;
419 new->prev = old;
421 new->next = NULL;
422 ts.tree_last = new;
424 else
426 /* Insert in to the middle of the list */
427 new->prev = old;
428 if (old)
430 /* Yes, in the middle */
431 new->next = old->next;
432 old->next = new;
434 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)
450 new->sublevel++;
451 new->subname = new->name + i + 1;
453 if (new->next)
454 submask = new->next->submask;
455 else
456 submask = 0;
457 submask |= 1 << new->sublevel;
458 submask &= (2 << new->sublevel) - 1;
459 new->submask = submask;
460 new->mark = 0;
462 /* Correct the submasks of the previous entries */
463 current = new->prev;
464 while (current && current->sublevel > new->sublevel)
466 current->submask |= 1 << new->sublevel;
467 current = current->prev;
470 /* The entry has now been added */
472 if (new->sublevel > 1)
474 /* Let's check if the parent directory is in the tree */
475 char *parent = g_strdup (new->name);
477 for (i = strlen (parent) - 1; i > 1; i--)
479 if (parent[i] == PATH_SEP)
481 parent[i] = 0;
482 tree_store_add_entry (parent);
483 break;
486 g_free (parent);
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);
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 char *dir)
581 static GList *special_dirs = NULL;
582 GList *l;
583 static gboolean loaded = FALSE;
585 if (!loaded)
587 loaded = TRUE;
588 setup_init ();
589 process_special_dirs (&special_dirs, profile_name);
590 process_special_dirs (&special_dirs, global_profile_name);
593 for (l = special_dirs; l != NULL; l = g_list_next (l))
594 if (strncmp (dir, l->data, strlen (l->data)) == 0)
595 return TRUE;
597 return FALSE;
600 /* --------------------------------------------------------------------------------------------- */
601 /*** public functions ****************************************************************************/
602 /* --------------------------------------------------------------------------------------------- */
604 /* Searches for specified directory */
605 tree_entry *
606 tree_store_whereis (const char *name)
608 tree_entry *current = ts.tree_first;
609 int flag = -1;
611 while (current && (flag = pathcmp (current->name, name)) < 0)
612 current = current->next;
614 if (flag == 0)
615 return current;
616 else
617 return NULL;
620 /* --------------------------------------------------------------------------------------------- */
622 struct TreeStore *
623 tree_store_get (void)
625 return &ts;
628 /* --------------------------------------------------------------------------------------------- */
630 * \fn int tree_store_load(void)
631 * \brief Loads the tree from the default location
632 * \return 1 if success (true), 0 otherwise (false)
636 tree_store_load (void)
638 char *name;
639 int retval;
641 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
642 retval = tree_store_load_from (name);
643 g_free (name);
645 return retval;
648 /* --------------------------------------------------------------------------------------------- */
650 * \fn int tree_store_save(void)
651 * \brief Saves the tree to the default file in an atomic fashion
652 * \return 0 if success, errno on error
656 tree_store_save (void)
658 char *name;
659 int retval;
661 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
662 mc_util_make_backup_if_possible (name, ".tmp");
664 retval = tree_store_save_to (name);
665 if (retval != 0)
667 mc_util_restore_from_backup_if_possible (name, ".tmp");
668 g_free (name);
669 return retval;
672 mc_util_unlink_backup_if_possible (name, ".tmp");
673 g_free (name);
674 return 0;
677 /* --------------------------------------------------------------------------------------------- */
679 void
680 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
682 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
685 /* --------------------------------------------------------------------------------------------- */
687 void
688 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
690 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
694 /* --------------------------------------------------------------------------------------------- */
696 void
697 tree_store_remove_entry (const char *name)
699 tree_entry *current, *base, *old;
700 int len;
702 g_return_if_fail (name != NULL);
704 /* Miguel Ugly hack */
705 if (name[0] == PATH_SEP && name[1] == 0)
706 return;
707 /* Miguel Ugly hack end */
709 base = tree_store_whereis (name);
710 if (!base)
711 return; /* Doesn't exist */
713 len = strlen (base->name);
714 current = base->next;
715 while (current
716 && strncmp (current->name, base->name, len) == 0
717 && (current->name[len] == '\0' || current->name[len] == PATH_SEP))
719 old = current;
720 current = current->next;
721 remove_entry (old);
723 remove_entry (base);
724 tree_store_dirty (TRUE);
726 return;
729 /* --------------------------------------------------------------------------------------------- */
730 /** This subdirectory exists -> clear deletion mark */
732 void
733 tree_store_mark_checked (const char *subname)
735 char *name;
736 tree_entry *current, *base;
737 int flag = 1, len;
738 if (!ts.loaded)
739 return;
741 if (ts.check_name == NULL)
742 return;
744 /* Calculate the full name of the subdirectory */
745 if (subname[0] == '.' && (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
746 return;
747 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
748 name = g_strconcat (PATH_SEP_STR, subname, (char *) NULL);
749 else
750 name = concat_dir_and_file (ts.check_name, subname);
752 /* Search for the subdirectory */
753 current = ts.check_start;
754 while (current && (flag = pathcmp (current->name, name)) < 0)
755 current = current->next;
757 if (flag != 0)
759 /* Doesn't exist -> add it */
760 current = tree_store_add_entry (name);
761 ts.add_queue = g_list_prepend (ts.add_queue, g_strdup (name));
763 g_free (name);
765 /* Clear the deletion mark from the subdirectory and its children */
766 base = current;
767 if (base)
769 len = strlen (base->name);
770 base->mark = 0;
771 current = base->next;
772 while (current
773 && strncmp (current->name, base->name, len) == 0
774 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
776 current->mark = 0;
777 current = current->next;
782 /* --------------------------------------------------------------------------------------------- */
783 /** Mark the subdirectories of the current directory for delete */
785 tree_entry *
786 tree_store_start_check (const char *path)
788 tree_entry *current, *retval;
789 int len;
791 if (!ts.loaded)
792 return NULL;
794 g_return_val_if_fail (ts.check_name == NULL, NULL);
795 ts.check_start = NULL;
797 /* Search for the start of subdirectories */
798 current = tree_store_whereis (path);
799 if (!current)
801 struct stat s;
803 if (mc_stat (path, &s) == -1)
804 return NULL;
806 if (!S_ISDIR (s.st_mode))
807 return NULL;
809 current = tree_store_add_entry (path);
810 ts.check_name = g_strdup (path);
812 return current;
815 ts.check_name = g_strdup (path);
817 retval = current;
819 /* Mark old subdirectories for delete */
820 ts.check_start = current->next;
821 len = strlen (ts.check_name);
823 current = ts.check_start;
824 while (current
825 && strncmp (current->name, ts.check_name, len) == 0
826 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
828 current->mark = 1;
829 current = current->next;
832 return retval;
835 /* --------------------------------------------------------------------------------------------- */
836 /** Delete subdirectories which still have the deletion mark */
838 void
839 tree_store_end_check (void)
841 tree_entry *current, *old;
842 size_t len;
843 GList *the_queue;
845 if (!ts.loaded)
846 return;
848 g_return_if_fail (ts.check_name != NULL);
850 /* Check delete marks and delete if found */
851 len = strlen (ts.check_name);
853 current = ts.check_start;
854 while (current
855 && strncmp (current->name, ts.check_name, len) == 0
856 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
858 old = current;
859 current = current->next;
860 if (old->mark)
861 remove_entry (old);
864 /* get the stuff in the scan order */
865 ts.add_queue = g_list_reverse (ts.add_queue);
866 the_queue = ts.add_queue;
867 ts.add_queue = NULL;
868 g_free (ts.check_name);
869 ts.check_name = NULL;
871 g_list_foreach (the_queue, (GFunc) g_free, NULL);
872 g_list_free (the_queue);
875 /* --------------------------------------------------------------------------------------------- */
877 tree_entry *
878 tree_store_rescan (const char *dir)
880 DIR *dirp;
881 struct dirent *dp;
882 struct stat buf;
883 tree_entry *entry;
885 if (should_skip_directory (dir))
887 entry = tree_store_add_entry (dir);
888 entry->scanned = 1;
890 return entry;
893 entry = tree_store_start_check (dir);
895 if (!entry)
896 return NULL;
898 dirp = mc_opendir (dir);
899 if (dirp)
901 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
903 char *full_name;
905 if (dp->d_name[0] == '.')
907 if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
908 continue;
911 full_name = concat_dir_and_file (dir, dp->d_name);
912 if (mc_lstat (full_name, &buf) != -1)
914 if (S_ISDIR (buf.st_mode))
915 tree_store_mark_checked (dp->d_name);
917 g_free (full_name);
919 mc_closedir (dirp);
921 tree_store_end_check ();
922 entry->scanned = 1;
924 return entry;
927 /* --------------------------------------------------------------------------------------------- */