Revert "Try fix of compile warnings about assigned but unused variables"
[midnight-commander.git] / src / filemanager / treestore.c
blob5faf6e199b693424f99114f2000c43545432fe7f
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) + 1;
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;
470 if (new->next)
471 submask = new->next->submask;
472 else
473 submask = 0;
474 submask |= 1 << new->sublevel;
475 submask &= (2 << new->sublevel) - 1;
476 new->submask = submask;
477 new->mark = 0;
479 /* Correct the submasks of the previous entries */
480 current = new->prev;
481 while (current && current->sublevel > new->sublevel)
483 current->submask |= 1 << new->sublevel;
484 current = current->prev;
487 /* The entry has now been added */
489 if (new->sublevel > 1)
491 /* Let's check if the parent directory is in the tree */
492 vfs_path_t *tmp_vpath;
494 tmp_vpath = vfs_path_vtokens_get (new->name, 0, new->sublevel - 1);
495 tree_store_add_entry (tmp_vpath);
496 vfs_path_free (tmp_vpath);
499 tree_store_dirty (TRUE);
500 return new;
503 /* --------------------------------------------------------------------------------------------- */
505 static void
506 tree_store_notify_remove (tree_entry * entry)
508 hook_t *p = remove_entry_hooks;
509 tree_store_remove_fn r;
511 while (p != NULL)
513 r = (tree_store_remove_fn) p->hook_fn;
514 r (entry, p->hook_data);
515 p = p->next;
519 /* --------------------------------------------------------------------------------------------- */
521 static tree_entry *
522 remove_entry (tree_entry * entry)
524 tree_entry *current = entry->prev;
525 long submask = 0;
526 tree_entry *ret = NULL;
528 tree_store_notify_remove (entry);
530 /* Correct the submasks of the previous entries */
531 if (entry->next)
532 submask = entry->next->submask;
533 while (current && current->sublevel > entry->sublevel)
535 submask |= 1 << current->sublevel;
536 submask &= (2 << current->sublevel) - 1;
537 current->submask = submask;
538 current = current->prev;
541 /* Unlink the entry from the list */
542 if (entry->prev)
543 entry->prev->next = entry->next;
544 else
545 ts.tree_first = entry->next;
547 if (entry->next)
548 entry->next->prev = entry->prev;
549 else
550 ts.tree_last = entry->prev;
552 /* Free the memory used by the entry */
553 g_free (entry->name);
554 g_free (entry);
556 return ret;
559 /* --------------------------------------------------------------------------------------------- */
561 static void
562 process_special_dirs (GList ** special_dirs, char *file)
564 gchar **buffers, **start_buff;
565 mc_config_t *cfg;
566 gsize buffers_len;
568 cfg = mc_config_init (file);
569 if (cfg == NULL)
570 return;
572 start_buff = buffers = mc_config_get_string_list (cfg, "Special dirs", "list", &buffers_len);
573 if (buffers != NULL)
575 while (*buffers != NULL)
577 *special_dirs = g_list_prepend (*special_dirs, *buffers);
578 *buffers = NULL;
579 buffers++;
581 g_strfreev (start_buff);
583 mc_config_deinit (cfg);
586 /* --------------------------------------------------------------------------------------------- */
588 static gboolean
589 should_skip_directory (const vfs_path_t * vpath)
591 static GList *special_dirs = NULL;
592 GList *l;
593 static gboolean loaded = FALSE;
594 char *dir;
595 gboolean ret = FALSE;
597 if (!loaded)
599 loaded = TRUE;
600 setup_init ();
601 process_special_dirs (&special_dirs, profile_name);
602 process_special_dirs (&special_dirs, global_profile_name);
605 dir = vfs_path_to_str (vpath);
606 for (l = special_dirs; l != NULL; l = g_list_next (l))
607 if (strncmp (dir, l->data, strlen (l->data)) == 0)
609 ret = TRUE;
610 break;
613 g_free (dir);
614 return ret;
617 /* --------------------------------------------------------------------------------------------- */
618 /*** public functions ****************************************************************************/
619 /* --------------------------------------------------------------------------------------------- */
621 /* Searches for specified directory */
622 tree_entry *
623 tree_store_whereis (const vfs_path_t * name)
625 tree_entry *current = ts.tree_first;
626 int flag = -1;
628 while (current && (flag = pathcmp (current->name, name)) < 0)
629 current = current->next;
631 if (flag == 0)
632 return current;
633 else
634 return NULL;
637 /* --------------------------------------------------------------------------------------------- */
639 struct TreeStore *
640 tree_store_get (void)
642 return &ts;
645 /* --------------------------------------------------------------------------------------------- */
647 * \fn int tree_store_load(void)
648 * \brief Loads the tree from the default location
649 * \return 1 if success (true), 0 otherwise (false)
653 tree_store_load (void)
655 char *name;
656 int retval;
658 name = mc_config_get_full_path (MC_TREESTORE_FILE);
659 retval = tree_store_load_from (name);
660 g_free (name);
662 return retval;
665 /* --------------------------------------------------------------------------------------------- */
667 * \fn int tree_store_save(void)
668 * \brief Saves the tree to the default file in an atomic fashion
669 * \return 0 if success, errno on error
673 tree_store_save (void)
675 char *name;
676 int retval;
678 name = mc_config_get_full_path (MC_TREESTORE_FILE);
679 mc_util_make_backup_if_possible (name, ".tmp");
681 retval = tree_store_save_to (name);
682 if (retval != 0)
684 mc_util_restore_from_backup_if_possible (name, ".tmp");
685 g_free (name);
686 return retval;
689 mc_util_unlink_backup_if_possible (name, ".tmp");
690 g_free (name);
691 return 0;
694 /* --------------------------------------------------------------------------------------------- */
696 void
697 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
699 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
702 /* --------------------------------------------------------------------------------------------- */
704 void
705 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
707 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
711 /* --------------------------------------------------------------------------------------------- */
713 void
714 tree_store_remove_entry (const vfs_path_t * name_vpath)
716 tree_entry *current, *base, *old;
717 size_t len;
719 g_return_if_fail (name_vpath != NULL);
721 /* Miguel Ugly hack */
723 char *name;
724 gboolean is_root;
726 name = vfs_path_to_str (name_vpath);
727 is_root = (name[0] == PATH_SEP && name[1] == '\0');
728 g_free (name);
729 if (is_root)
730 return;
732 /* Miguel Ugly hack end */
734 base = tree_store_whereis (name_vpath);
735 if (!base)
736 return; /* Doesn't exist */
738 len = vfs_path_len (base->name);
739 current = base->next;
740 while (current != NULL && vfs_path_ncmp (current->name, base->name, len) == 0)
742 char *current_name;
743 gboolean ok;
745 current_name = vfs_path_to_str (current->name);
746 ok = (current_name[len] == '\0' || current_name[len] == PATH_SEP);
747 g_free (current_name);
748 if (!ok)
749 break;
750 old = current;
751 current = current->next;
752 remove_entry (old);
754 remove_entry (base);
755 tree_store_dirty (TRUE);
758 /* --------------------------------------------------------------------------------------------- */
759 /** This subdirectory exists -> clear deletion mark */
761 void
762 tree_store_mark_checked (const char *subname)
764 vfs_path_t *name;
765 char *check_name;
766 tree_entry *current, *base;
767 int flag = 1;
768 if (!ts.loaded)
769 return;
771 if (ts.check_name == NULL)
772 return;
774 /* Calculate the full name of the subdirectory */
775 if (subname[0] == '.' && (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
776 return;
778 check_name = vfs_path_to_str (ts.check_name);
779 if (check_name[0] == PATH_SEP && check_name[1] == 0)
780 name = vfs_path_build_filename (PATH_SEP_STR, subname, NULL);
781 else
782 name = vfs_path_append_new (ts.check_name, subname, NULL);
783 g_free (check_name);
785 /* Search for the subdirectory */
786 current = ts.check_start;
787 while (current && (flag = pathcmp (current->name, name)) < 0)
788 current = current->next;
790 if (flag != 0)
792 /* Doesn't exist -> add it */
793 current = tree_store_add_entry (name);
794 ts.add_queue_vpath = g_list_prepend (ts.add_queue_vpath, vfs_path_clone (name));
796 g_free (name);
798 /* Clear the deletion mark from the subdirectory and its children */
799 base = current;
800 if (base)
802 size_t len;
804 len = vfs_path_len (base->name);
805 base->mark = 0;
806 current = base->next;
807 while (current != NULL && vfs_path_ncmp (current->name, base->name, len) == 0)
809 gboolean ok;
811 check_name = vfs_path_to_str (current->name);
812 ok = (check_name[len] == '\0' || check_name[len] == PATH_SEP || len == 1);
813 g_free (check_name);
814 if (!ok)
815 break;
816 current->mark = 0;
817 current = current->next;
822 /* --------------------------------------------------------------------------------------------- */
823 /** Mark the subdirectories of the current directory for delete */
825 tree_entry *
826 tree_store_start_check (const vfs_path_t * vpath)
828 tree_entry *current, *retval;
829 size_t len;
831 if (!ts.loaded)
832 return NULL;
834 g_return_val_if_fail (ts.check_name == NULL, NULL);
835 ts.check_start = NULL;
837 /* Search for the start of subdirectories */
838 current = tree_store_whereis (vpath);
839 if (!current)
841 struct stat s;
843 if (mc_stat (vpath, &s) == -1)
844 return NULL;
846 if (!S_ISDIR (s.st_mode))
847 return NULL;
849 current = tree_store_add_entry (vpath);
850 ts.check_name = vfs_path_clone (vpath);
852 return current;
855 ts.check_name = vfs_path_clone (vpath);
857 retval = current;
859 /* Mark old subdirectories for delete */
860 ts.check_start = current->next;
861 len = vfs_path_len (ts.check_name);
863 current = ts.check_start;
864 while (current != NULL && vfs_path_cmp (current->name, ts.check_name) == 0)
866 char *current_name;
867 gboolean ok;
869 current_name = vfs_path_to_str (current->name);
870 ok = (current_name[len] == '\0' || (current_name[len] == PATH_SEP || len == 1));
871 g_free (current_name);
872 if (!ok)
873 break;
874 current->mark = 1;
875 current = current->next;
878 return retval;
881 /* --------------------------------------------------------------------------------------------- */
882 /** Delete subdirectories which still have the deletion mark */
884 void
885 tree_store_end_check (void)
887 tree_entry *current, *old;
888 size_t len;
889 GList *the_queue;
891 if (!ts.loaded)
892 return;
894 g_return_if_fail (ts.check_name != NULL);
896 /* Check delete marks and delete if found */
897 len = vfs_path_len (ts.check_name);
899 current = ts.check_start;
900 while (current != NULL && vfs_path_ncmp (current->name, ts.check_name, len) == 0)
902 char *current_name;
903 gboolean ok;
905 current_name = vfs_path_to_str (current->name);
906 ok = (current_name[len] == '\0' || current_name[len] == PATH_SEP || len == 1);
907 g_free (current_name);
909 if (!ok)
910 break;
911 old = current;
912 current = current->next;
913 if (old->mark)
914 remove_entry (old);
917 /* get the stuff in the scan order */
918 ts.add_queue_vpath = g_list_reverse (ts.add_queue_vpath);
919 the_queue = ts.add_queue_vpath;
920 ts.add_queue_vpath = NULL;
921 g_free (ts.check_name);
922 ts.check_name = NULL;
924 g_list_foreach (the_queue, (GFunc) vfs_path_free, NULL);
925 g_list_free (the_queue);
928 /* --------------------------------------------------------------------------------------------- */
930 tree_entry *
931 tree_store_rescan (const vfs_path_t * vpath)
933 DIR *dirp;
934 struct dirent *dp;
935 struct stat buf;
936 tree_entry *entry;
938 if (should_skip_directory (vpath))
940 entry = tree_store_add_entry (vpath);
941 entry->scanned = 1;
942 return entry;
945 entry = tree_store_start_check (vpath);
946 if (entry == NULL)
947 return NULL;
949 dirp = mc_opendir (vpath);
950 if (dirp)
952 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
954 vfs_path_t *tmp_vpath;
956 if (dp->d_name[0] == '.')
958 if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
959 continue;
962 tmp_vpath = vfs_path_append_new (vpath, dp->d_name, NULL);
963 if (mc_lstat (tmp_vpath, &buf) != -1)
965 if (S_ISDIR (buf.st_mode))
966 tree_store_mark_checked (dp->d_name);
968 vfs_path_free (tmp_vpath);
970 mc_closedir (dirp);
972 tree_store_end_check ();
973 entry->scanned = 1;
975 return entry;
978 /* --------------------------------------------------------------------------------------------- */