Changed interface of function mc_opendir()
[midnight-commander.git] / src / filemanager / treestore.c
blob7bd96a1bb021adb5532b54e1239e34a08f75eb0a
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/hook.h"
57 #include "lib/util.h"
59 #include "src/setup.h"
61 #include "treestore.h"
63 /*** global variables ****************************************************************************/
65 /*** file scope macro definitions ****************************************************************/
67 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
69 /*** file scope type declarations ****************************************************************/
71 /*** file scope variables ************************************************************************/
73 static struct TreeStore ts;
75 static hook_t *remove_entry_hooks;
77 /*** file scope functions ************************************************************************/
78 /* --------------------------------------------------------------------------------------------- */
80 static tree_entry *tree_store_add_entry (const char *name);
82 /* --------------------------------------------------------------------------------------------- */
84 static void
85 tree_store_dirty (int state)
87 ts.dirty = state;
90 /* --------------------------------------------------------------------------------------------- */
91 /** Returns the number of common bytes in the strings. */
93 static size_t
94 str_common (const char *s1, const char *s2)
96 size_t result = 0;
98 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
99 result++;
100 return result;
103 /* --------------------------------------------------------------------------------------------- */
104 /* The directory names are arranged in a single linked list in the same
105 order as they are displayed. When the tree is displayed the expected
106 order is like this:
108 /bin
109 /etc
110 /etc/X11
111 /etc/rc.d
112 /etc.old/X11
113 /etc.old/rc.d
114 /usr
116 i.e. the required collating sequence when comparing two directory names is
117 '\0' < PATH_SEP < all-other-characters-in-encoding-order
119 Since strcmp doesn't fulfil this requirement we use pathcmp when
120 inserting directory names into the list. The meaning of the return value
121 of pathcmp and strcmp are the same (an integer less than, equal to, or
122 greater than zero if p1 is found to be less than, to match, or be greater
123 than p2.
126 static int
127 pathcmp (const char *p1, const char *p2)
129 for (; *p1 == *p2; p1++, p2++)
130 if (*p1 == '\0')
131 return 0;
133 if (*p1 == '\0')
134 return -1;
135 if (*p2 == '\0')
136 return 1;
137 if (*p1 == PATH_SEP)
138 return -1;
139 if (*p2 == PATH_SEP)
140 return 1;
141 return (*p1 - *p2);
144 /* --------------------------------------------------------------------------------------------- */
146 static char *
147 decode (char *buffer)
149 char *res = g_strdup (buffer);
150 char *p, *q;
152 for (p = q = res; *p; p++, q++)
154 if (*p == '\n')
156 *q = 0;
157 return res;
160 if (*p != '\\')
162 *q = *p;
163 continue;
166 p++;
168 switch (*p)
170 case 'n':
171 *q = '\n';
172 break;
173 case '\\':
174 *q = '\\';
175 break;
178 *q = *p;
180 return res;
183 /* --------------------------------------------------------------------------------------------- */
184 /** Loads the tree store from the specified filename */
186 static int
187 tree_store_load_from (char *name)
189 FILE *file;
190 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
191 char *different;
192 int common;
193 int do_load;
195 g_return_val_if_fail (name != NULL, FALSE);
197 if (ts.loaded)
198 return TRUE;
200 file = fopen (name, "r");
202 if (file)
204 if (fgets (buffer, sizeof (buffer), file) != NULL)
206 if (strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0)
208 fclose (file);
209 do_load = FALSE;
211 else
212 do_load = TRUE;
214 else
215 do_load = FALSE;
217 else
218 do_load = FALSE;
220 if (do_load)
222 ts.loaded = TRUE;
224 /* File open -> read contents */
225 oldname[0] = 0;
226 while (fgets (buffer, MC_MAXPATHLEN, file))
228 tree_entry *e;
229 int scanned;
230 char *lc_name;
232 /* Skip invalid records */
233 if ((buffer[0] != '0' && buffer[0] != '1'))
234 continue;
236 if (buffer[1] != ':')
237 continue;
239 scanned = buffer[0] == '1';
241 lc_name = decode (buffer + 2);
242 if (lc_name[0] != PATH_SEP)
244 /* Clear-text decompression */
245 char *s = strtok (lc_name, " ");
247 if (s)
249 common = atoi (s);
250 different = strtok (NULL, "");
251 if (different)
253 vfs_path_t *vpath = vfs_path_from_str (oldname);
254 strcpy (oldname + common, different);
255 if (vfs_file_is_local (vpath))
257 e = tree_store_add_entry (oldname);
258 e->scanned = scanned;
260 vfs_path_free (vpath);
264 else
266 vfs_path_t *vpath = vfs_path_from_str (lc_name);
267 if (vfs_file_is_local (vpath))
269 e = tree_store_add_entry (lc_name);
270 e->scanned = scanned;
272 vfs_path_free (vpath);
273 strcpy (oldname, lc_name);
275 g_free (lc_name);
277 fclose (file);
280 /* Nothing loaded, we add some standard directories */
281 if (!ts.tree_first)
283 vfs_path_t *tmp_vpath = vfs_path_from_str (PATH_SEP_STR);
284 tree_store_add_entry (PATH_SEP_STR);
285 tree_store_rescan (tmp_vpath);
286 vfs_path_free (tmp_vpath);
287 ts.loaded = TRUE;
290 return TRUE;
293 /* --------------------------------------------------------------------------------------------- */
295 static char *
296 encode (const char *string)
298 int special_chars;
299 const char *p;
300 char *q;
301 char *res;
303 for (special_chars = 0, p = string; *p; p++)
305 if (*p == '\n' || *p == '\\')
306 special_chars++;
309 res = g_malloc (p - string + special_chars + 1);
310 for (p = string, q = res; *p; p++, q++)
312 if (*p != '\n' && *p != '\\')
314 *q = *p;
315 continue;
318 *q++ = '\\';
320 switch (*p)
322 case '\n':
323 *q = 'n';
324 break;
326 case '\\':
327 *q = '\\';
328 break;
331 *q = 0;
332 return res;
335 /* --------------------------------------------------------------------------------------------- */
336 /** Saves the tree to the specified filename */
338 static int
339 tree_store_save_to (char *name)
341 tree_entry *current;
342 FILE *file;
344 file = fopen (name, "w");
345 if (!file)
346 return errno;
348 fprintf (file, "%s\n", TREE_SIGNATURE);
350 current = ts.tree_first;
351 while (current)
353 int i, common;
354 vfs_path_t *vpath = vfs_path_from_str (current->name);
356 if (vfs_file_is_local (vpath))
358 /* Clear-text compression */
359 if (current->prev && (common = str_common (current->prev->name, current->name)) > 2)
361 char *encoded = encode (current->name + common);
363 i = fprintf (file, "%d:%d %s\n", current->scanned, common, encoded);
364 g_free (encoded);
366 else
368 char *encoded = encode (current->name);
370 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
371 g_free (encoded);
374 if (i == EOF)
376 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
377 name, unix_error_string (errno));
378 vfs_path_free (vpath);
379 break;
382 vfs_path_free (vpath);
383 current = current->next;
385 tree_store_dirty (FALSE);
386 fclose (file);
388 return 0;
391 /* --------------------------------------------------------------------------------------------- */
393 static tree_entry *
394 tree_store_add_entry (const char *name)
396 int flag = -1;
397 tree_entry *current = ts.tree_first;
398 tree_entry *old = NULL;
399 tree_entry *new;
400 int i, len;
401 int submask = 0;
403 if (ts.tree_last && ts.tree_last->next)
404 abort ();
406 /* Search for the correct place */
407 while (current && (flag = pathcmp (current->name, name)) < 0)
409 old = current;
410 current = current->next;
413 if (flag == 0)
414 return current; /* Already in the list */
416 /* Not in the list -> add it */
417 new = g_new0 (tree_entry, 1);
418 if (!current)
420 /* Append to the end of the list */
421 if (!ts.tree_first)
423 /* Empty list */
424 ts.tree_first = new;
425 new->prev = NULL;
427 else
429 old->next = new;
430 new->prev = old;
432 new->next = NULL;
433 ts.tree_last = new;
435 else
437 /* Insert in to the middle of the list */
438 new->prev = old;
439 if (old)
441 /* Yes, in the middle */
442 new->next = old->next;
443 old->next = new;
445 else
447 /* Nope, in the beginning of the list */
448 new->next = ts.tree_first;
449 ts.tree_first = new;
451 new->next->prev = new;
454 /* Calculate attributes */
455 new->name = g_strdup (name);
456 len = strlen (new->name);
457 new->sublevel = 0;
458 for (i = 0; i < len; i++)
459 if (new->name[i] == PATH_SEP)
461 new->sublevel++;
462 new->subname = new->name + i + 1;
464 if (new->next)
465 submask = new->next->submask;
466 else
467 submask = 0;
468 submask |= 1 << new->sublevel;
469 submask &= (2 << new->sublevel) - 1;
470 new->submask = submask;
471 new->mark = 0;
473 /* Correct the submasks of the previous entries */
474 current = new->prev;
475 while (current && current->sublevel > new->sublevel)
477 current->submask |= 1 << new->sublevel;
478 current = current->prev;
481 /* The entry has now been added */
483 if (new->sublevel > 1)
485 /* Let's check if the parent directory is in the tree */
486 char *parent = g_strdup (new->name);
488 for (i = strlen (parent) - 1; i > 1; i--)
490 if (parent[i] == PATH_SEP)
492 parent[i] = 0;
493 tree_store_add_entry (parent);
494 break;
497 g_free (parent);
500 tree_store_dirty (TRUE);
501 return new;
504 /* --------------------------------------------------------------------------------------------- */
506 static void
507 tree_store_notify_remove (tree_entry * entry)
509 hook_t *p = remove_entry_hooks;
510 tree_store_remove_fn r;
512 while (p != NULL)
514 r = (tree_store_remove_fn) p->hook_fn;
515 r (entry, p->hook_data);
516 p = p->next;
520 /* --------------------------------------------------------------------------------------------- */
522 static tree_entry *
523 remove_entry (tree_entry * entry)
525 tree_entry *current = entry->prev;
526 long submask = 0;
527 tree_entry *ret = NULL;
529 tree_store_notify_remove (entry);
531 /* Correct the submasks of the previous entries */
532 if (entry->next)
533 submask = entry->next->submask;
534 while (current && current->sublevel > entry->sublevel)
536 submask |= 1 << current->sublevel;
537 submask &= (2 << current->sublevel) - 1;
538 current->submask = submask;
539 current = current->prev;
542 /* Unlink the entry from the list */
543 if (entry->prev)
544 entry->prev->next = entry->next;
545 else
546 ts.tree_first = entry->next;
548 if (entry->next)
549 entry->next->prev = entry->prev;
550 else
551 ts.tree_last = entry->prev;
553 /* Free the memory used by the entry */
554 g_free (entry->name);
555 g_free (entry);
557 return ret;
560 /* --------------------------------------------------------------------------------------------- */
562 static void
563 process_special_dirs (GList ** special_dirs, char *file)
565 gchar **buffers, **start_buff;
566 mc_config_t *cfg;
567 gsize buffers_len;
569 cfg = mc_config_init (file);
570 if (cfg == NULL)
571 return;
573 start_buff = buffers = mc_config_get_string_list (cfg, "Special dirs", "list", &buffers_len);
574 if (buffers != NULL)
576 while (*buffers != NULL)
578 *special_dirs = g_list_prepend (*special_dirs, *buffers);
579 *buffers = NULL;
580 buffers++;
582 g_strfreev (start_buff);
584 mc_config_deinit (cfg);
587 /* --------------------------------------------------------------------------------------------- */
589 static gboolean
590 should_skip_directory (const char *dir)
592 static GList *special_dirs = NULL;
593 GList *l;
594 static gboolean loaded = 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 for (l = special_dirs; l != NULL; l = g_list_next (l))
605 if (strncmp (dir, l->data, strlen (l->data)) == 0)
606 return TRUE;
608 return FALSE;
611 /* --------------------------------------------------------------------------------------------- */
612 /*** public functions ****************************************************************************/
613 /* --------------------------------------------------------------------------------------------- */
615 /* Searches for specified directory */
616 tree_entry *
617 tree_store_whereis (const char *name)
619 tree_entry *current = ts.tree_first;
620 int flag = -1;
622 while (current && (flag = pathcmp (current->name, name)) < 0)
623 current = current->next;
625 if (flag == 0)
626 return current;
627 else
628 return NULL;
631 /* --------------------------------------------------------------------------------------------- */
633 struct TreeStore *
634 tree_store_get (void)
636 return &ts;
639 /* --------------------------------------------------------------------------------------------- */
641 * \fn int tree_store_load(void)
642 * \brief Loads the tree from the default location
643 * \return 1 if success (true), 0 otherwise (false)
647 tree_store_load (void)
649 char *name;
650 int retval;
652 name = mc_config_get_full_path (MC_TREESTORE_FILE);
653 retval = tree_store_load_from (name);
654 g_free (name);
656 return retval;
659 /* --------------------------------------------------------------------------------------------- */
661 * \fn int tree_store_save(void)
662 * \brief Saves the tree to the default file in an atomic fashion
663 * \return 0 if success, errno on error
667 tree_store_save (void)
669 char *name;
670 int retval;
672 name = mc_config_get_full_path (MC_TREESTORE_FILE);
673 mc_util_make_backup_if_possible (name, ".tmp");
675 retval = tree_store_save_to (name);
676 if (retval != 0)
678 mc_util_restore_from_backup_if_possible (name, ".tmp");
679 g_free (name);
680 return retval;
683 mc_util_unlink_backup_if_possible (name, ".tmp");
684 g_free (name);
685 return 0;
688 /* --------------------------------------------------------------------------------------------- */
690 void
691 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
693 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
696 /* --------------------------------------------------------------------------------------------- */
698 void
699 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
701 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
705 /* --------------------------------------------------------------------------------------------- */
707 void
708 tree_store_remove_entry (const char *name)
710 tree_entry *current, *base, *old;
711 int len;
713 g_return_if_fail (name != NULL);
715 /* Miguel Ugly hack */
716 if (name[0] == PATH_SEP && name[1] == 0)
717 return;
718 /* Miguel Ugly hack end */
720 base = tree_store_whereis (name);
721 if (!base)
722 return; /* Doesn't exist */
724 len = strlen (base->name);
725 current = base->next;
726 while (current
727 && strncmp (current->name, base->name, len) == 0
728 && (current->name[len] == '\0' || current->name[len] == PATH_SEP))
730 old = current;
731 current = current->next;
732 remove_entry (old);
734 remove_entry (base);
735 tree_store_dirty (TRUE);
737 return;
740 /* --------------------------------------------------------------------------------------------- */
741 /** This subdirectory exists -> clear deletion mark */
743 void
744 tree_store_mark_checked (const char *subname)
746 char *name;
747 tree_entry *current, *base;
748 int flag = 1, len;
749 if (!ts.loaded)
750 return;
752 if (ts.check_name == NULL)
753 return;
755 /* Calculate the full name of the subdirectory */
756 if (subname[0] == '.' && (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
757 return;
758 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
759 name = g_strconcat (PATH_SEP_STR, subname, (char *) NULL);
760 else
761 name = concat_dir_and_file (ts.check_name, subname);
763 /* Search for the subdirectory */
764 current = ts.check_start;
765 while (current && (flag = pathcmp (current->name, name)) < 0)
766 current = current->next;
768 if (flag != 0)
770 /* Doesn't exist -> add it */
771 current = tree_store_add_entry (name);
772 ts.add_queue = g_list_prepend (ts.add_queue, g_strdup (name));
774 g_free (name);
776 /* Clear the deletion mark from the subdirectory and its children */
777 base = current;
778 if (base)
780 len = strlen (base->name);
781 base->mark = 0;
782 current = base->next;
783 while (current
784 && strncmp (current->name, base->name, len) == 0
785 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
787 current->mark = 0;
788 current = current->next;
793 /* --------------------------------------------------------------------------------------------- */
794 /** Mark the subdirectories of the current directory for delete */
796 tree_entry *
797 tree_store_start_check (const char *path)
799 tree_entry *current, *retval;
800 size_t len;
802 if (!ts.loaded)
803 return NULL;
805 g_return_val_if_fail (ts.check_name == NULL, NULL);
806 ts.check_start = NULL;
808 /* Search for the start of subdirectories */
809 current = tree_store_whereis (path);
810 if (!current)
812 struct stat s;
813 vfs_path_t *vpath = vfs_path_from_str (path);
815 if (mc_stat (vpath, &s) == -1)
817 vfs_path_free (vpath);
818 return NULL;
820 vfs_path_free (vpath);
822 if (!S_ISDIR (s.st_mode))
823 return NULL;
825 current = tree_store_add_entry (path);
826 ts.check_name = g_strdup (path);
828 return current;
831 ts.check_name = g_strdup (path);
833 retval = current;
835 /* Mark old subdirectories for delete */
836 ts.check_start = current->next;
837 len = strlen (ts.check_name);
839 current = ts.check_start;
840 while (current
841 && strncmp (current->name, ts.check_name, len) == 0
842 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
844 current->mark = 1;
845 current = current->next;
848 return retval;
851 /* --------------------------------------------------------------------------------------------- */
852 /** Delete subdirectories which still have the deletion mark */
854 void
855 tree_store_end_check (void)
857 tree_entry *current, *old;
858 size_t len;
859 GList *the_queue;
861 if (!ts.loaded)
862 return;
864 g_return_if_fail (ts.check_name != NULL);
866 /* Check delete marks and delete if found */
867 len = strlen (ts.check_name);
869 current = ts.check_start;
870 while (current
871 && strncmp (current->name, ts.check_name, len) == 0
872 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
874 old = current;
875 current = current->next;
876 if (old->mark)
877 remove_entry (old);
880 /* get the stuff in the scan order */
881 ts.add_queue = g_list_reverse (ts.add_queue);
882 the_queue = ts.add_queue;
883 ts.add_queue = NULL;
884 g_free (ts.check_name);
885 ts.check_name = NULL;
887 g_list_foreach (the_queue, (GFunc) g_free, NULL);
888 g_list_free (the_queue);
891 /* --------------------------------------------------------------------------------------------- */
893 tree_entry *
894 tree_store_rescan (const vfs_path_t * vpath)
896 DIR *dirp;
897 struct dirent *dp;
898 struct stat buf;
899 tree_entry *entry;
900 char *dir = vfs_path_to_str (vpath);
902 if (should_skip_directory (dir))
904 entry = tree_store_add_entry (dir);
905 entry->scanned = 1;
906 g_free (dir);
907 return entry;
910 entry = tree_store_start_check (dir);
912 if (!entry)
914 g_free (dir);
915 return NULL;
918 dirp = mc_opendir (vpath);
919 if (dirp)
921 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
923 vfs_path_t *tmp_vpath;
925 if (dp->d_name[0] == '.')
927 if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
928 continue;
931 tmp_vpath = vfs_path_append_new (vpath, dp->d_name, NULL);
932 if (mc_lstat (tmp_vpath, &buf) != -1)
934 if (S_ISDIR (buf.st_mode))
935 tree_store_mark_checked (dp->d_name);
937 vfs_path_free (tmp_vpath);
939 mc_closedir (dirp);
941 tree_store_end_check ();
942 entry->scanned = 1;
943 g_free (dir);
945 return entry;
948 /* --------------------------------------------------------------------------------------------- */