Merge branch '2499_mcedit_select_cur_word'
[midnight-commander/borarpet.git] / src / filemanager / treestore.c
blob57cdf2e80555107a4a645b6d5c1a112a5446c413
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 len, 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);
241 len = strlen (lc_name);
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 strcpy (oldname + common, different);
254 if (vfs_file_is_local (oldname))
256 e = tree_store_add_entry (oldname);
257 e->scanned = scanned;
262 else
264 if (vfs_file_is_local (lc_name))
266 e = tree_store_add_entry (lc_name);
267 e->scanned = scanned;
269 strcpy (oldname, lc_name);
271 g_free (lc_name);
273 fclose (file);
276 /* Nothing loaded, we add some standard directories */
277 if (!ts.tree_first)
279 tree_store_add_entry (PATH_SEP_STR);
280 tree_store_rescan (PATH_SEP_STR);
281 ts.loaded = TRUE;
284 return TRUE;
287 /* --------------------------------------------------------------------------------------------- */
289 static char *
290 encode (const char *string)
292 int special_chars;
293 const char *p;
294 char *q;
295 char *res;
297 for (special_chars = 0, p = string; *p; p++)
299 if (*p == '\n' || *p == '\\')
300 special_chars++;
303 res = g_malloc (p - string + special_chars + 1);
304 for (p = string, q = res; *p; p++, q++)
306 if (*p != '\n' && *p != '\\')
308 *q = *p;
309 continue;
312 *q++ = '\\';
314 switch (*p)
316 case '\n':
317 *q = 'n';
318 break;
320 case '\\':
321 *q = '\\';
322 break;
325 *q = 0;
326 return res;
329 /* --------------------------------------------------------------------------------------------- */
330 /** Saves the tree to the specified filename */
332 static int
333 tree_store_save_to (char *name)
335 tree_entry *current;
336 FILE *file;
338 file = fopen (name, "w");
339 if (!file)
340 return errno;
342 fprintf (file, "%s\n", TREE_SIGNATURE);
344 current = ts.tree_first;
345 while (current)
347 int i, common;
349 if (vfs_file_is_local (current->name))
351 /* Clear-text compression */
352 if (current->prev && (common = str_common (current->prev->name, current->name)) > 2)
354 char *encoded = encode (current->name + common);
356 i = fprintf (file, "%d:%d %s\n", current->scanned, common, encoded);
357 g_free (encoded);
359 else
361 char *encoded = encode (current->name);
363 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
364 g_free (encoded);
367 if (i == EOF)
369 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
370 name, unix_error_string (errno));
371 break;
374 current = current->next;
376 tree_store_dirty (FALSE);
377 fclose (file);
379 return 0;
382 /* --------------------------------------------------------------------------------------------- */
384 static tree_entry *
385 tree_store_add_entry (const char *name)
387 int flag = -1;
388 tree_entry *current = ts.tree_first;
389 tree_entry *old = NULL;
390 tree_entry *new;
391 int i, len;
392 int submask = 0;
394 if (ts.tree_last && ts.tree_last->next)
395 abort ();
397 /* Search for the correct place */
398 while (current && (flag = pathcmp (current->name, name)) < 0)
400 old = current;
401 current = current->next;
404 if (flag == 0)
405 return current; /* Already in the list */
407 /* Not in the list -> add it */
408 new = g_new0 (tree_entry, 1);
409 if (!current)
411 /* Append to the end of the list */
412 if (!ts.tree_first)
414 /* Empty list */
415 ts.tree_first = new;
416 new->prev = NULL;
418 else
420 old->next = new;
421 new->prev = old;
423 new->next = NULL;
424 ts.tree_last = new;
426 else
428 /* Insert in to the middle of the list */
429 new->prev = old;
430 if (old)
432 /* Yes, in the middle */
433 new->next = old->next;
434 old->next = new;
436 else
438 /* Nope, in the beginning of the list */
439 new->next = ts.tree_first;
440 ts.tree_first = new;
442 new->next->prev = new;
445 /* Calculate attributes */
446 new->name = g_strdup (name);
447 len = strlen (new->name);
448 new->sublevel = 0;
449 for (i = 0; i < len; i++)
450 if (new->name[i] == PATH_SEP)
452 new->sublevel++;
453 new->subname = new->name + i + 1;
455 if (new->next)
456 submask = new->next->submask;
457 else
458 submask = 0;
459 submask |= 1 << new->sublevel;
460 submask &= (2 << new->sublevel) - 1;
461 new->submask = submask;
462 new->mark = 0;
464 /* Correct the submasks of the previous entries */
465 current = new->prev;
466 while (current && current->sublevel > new->sublevel)
468 current->submask |= 1 << new->sublevel;
469 current = current->prev;
472 /* The entry has now been added */
474 if (new->sublevel > 1)
476 /* Let's check if the parent directory is in the tree */
477 char *parent = g_strdup (new->name);
479 for (i = strlen (parent) - 1; i > 1; i--)
481 if (parent[i] == PATH_SEP)
483 parent[i] = 0;
484 tree_store_add_entry (parent);
485 break;
488 g_free (parent);
491 tree_store_dirty (TRUE);
492 return new;
495 /* --------------------------------------------------------------------------------------------- */
497 static void
498 tree_store_notify_remove (tree_entry * entry)
500 hook_t *p = remove_entry_hooks;
501 tree_store_remove_fn r;
503 while (p != NULL)
505 r = (tree_store_remove_fn) p->hook_fn;
506 r (entry, p->hook_data);
507 p = p->next;
511 /* --------------------------------------------------------------------------------------------- */
513 static tree_entry *
514 remove_entry (tree_entry * entry)
516 tree_entry *current = entry->prev;
517 long submask = 0;
518 tree_entry *ret = NULL;
520 tree_store_notify_remove (entry);
522 /* Correct the submasks of the previous entries */
523 if (entry->next)
524 submask = entry->next->submask;
525 while (current && current->sublevel > entry->sublevel)
527 submask |= 1 << current->sublevel;
528 submask &= (2 << current->sublevel) - 1;
529 current->submask = submask;
530 current = current->prev;
533 /* Unlink the entry from the list */
534 if (entry->prev)
535 entry->prev->next = entry->next;
536 else
537 ts.tree_first = entry->next;
539 if (entry->next)
540 entry->next->prev = entry->prev;
541 else
542 ts.tree_last = entry->prev;
544 /* Free the memory used by the entry */
545 g_free (entry->name);
546 g_free (entry);
548 return ret;
551 /* --------------------------------------------------------------------------------------------- */
553 static void
554 process_special_dirs (GList ** special_dirs, char *file)
556 gchar **buffers, **start_buff;
557 mc_config_t *cfg;
558 gsize buffers_len;
560 cfg = mc_config_init (file);
561 if (cfg == NULL)
562 return;
564 start_buff = buffers = mc_config_get_string_list (cfg, "Special dirs", "list", &buffers_len);
565 if (buffers != NULL)
567 while (*buffers != NULL)
569 *special_dirs = g_list_prepend (*special_dirs, *buffers);
570 *buffers = NULL;
571 buffers++;
573 g_strfreev (start_buff);
575 mc_config_deinit (cfg);
578 /* --------------------------------------------------------------------------------------------- */
580 static gboolean
581 should_skip_directory (const char *dir)
583 static GList *special_dirs = NULL;
584 GList *l;
585 static gboolean loaded = FALSE;
587 if (!loaded)
589 loaded = TRUE;
590 setup_init ();
591 process_special_dirs (&special_dirs, profile_name);
592 process_special_dirs (&special_dirs, global_profile_name);
595 for (l = special_dirs; l != NULL; l = g_list_next (l))
596 if (strncmp (dir, l->data, strlen (l->data)) == 0)
597 return TRUE;
599 return FALSE;
602 /* --------------------------------------------------------------------------------------------- */
603 /*** public functions ****************************************************************************/
604 /* --------------------------------------------------------------------------------------------- */
606 /* Searches for specified directory */
607 tree_entry *
608 tree_store_whereis (const char *name)
610 tree_entry *current = ts.tree_first;
611 int flag = -1;
613 while (current && (flag = pathcmp (current->name, name)) < 0)
614 current = current->next;
616 if (flag == 0)
617 return current;
618 else
619 return NULL;
622 /* --------------------------------------------------------------------------------------------- */
624 struct TreeStore *
625 tree_store_get (void)
627 return &ts;
630 /* --------------------------------------------------------------------------------------------- */
632 * \fn int tree_store_load(void)
633 * \brief Loads the tree from the default location
634 * \return 1 if success (true), 0 otherwise (false)
638 tree_store_load (void)
640 char *name;
641 int retval;
643 name = g_build_filename (mc_config_get_cache_path (), MC_TREESTORE_FILE, NULL);
644 retval = tree_store_load_from (name);
645 g_free (name);
647 return retval;
650 /* --------------------------------------------------------------------------------------------- */
652 * \fn int tree_store_save(void)
653 * \brief Saves the tree to the default file in an atomic fashion
654 * \return 0 if success, errno on error
658 tree_store_save (void)
660 char *name;
661 int retval;
663 name = g_build_filename (mc_config_get_cache_path (), MC_TREESTORE_FILE, NULL);
664 mc_util_make_backup_if_possible (name, ".tmp");
666 retval = tree_store_save_to (name);
667 if (retval != 0)
669 mc_util_restore_from_backup_if_possible (name, ".tmp");
670 g_free (name);
671 return retval;
674 mc_util_unlink_backup_if_possible (name, ".tmp");
675 g_free (name);
676 return 0;
679 /* --------------------------------------------------------------------------------------------- */
681 void
682 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
684 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
687 /* --------------------------------------------------------------------------------------------- */
689 void
690 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
692 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
696 /* --------------------------------------------------------------------------------------------- */
698 void
699 tree_store_remove_entry (const char *name)
701 tree_entry *current, *base, *old;
702 int len;
704 g_return_if_fail (name != NULL);
706 /* Miguel Ugly hack */
707 if (name[0] == PATH_SEP && name[1] == 0)
708 return;
709 /* Miguel Ugly hack end */
711 base = tree_store_whereis (name);
712 if (!base)
713 return; /* Doesn't exist */
715 len = strlen (base->name);
716 current = base->next;
717 while (current
718 && strncmp (current->name, base->name, len) == 0
719 && (current->name[len] == '\0' || current->name[len] == PATH_SEP))
721 old = current;
722 current = current->next;
723 remove_entry (old);
725 remove_entry (base);
726 tree_store_dirty (TRUE);
728 return;
731 /* --------------------------------------------------------------------------------------------- */
732 /** This subdirectory exists -> clear deletion mark */
734 void
735 tree_store_mark_checked (const char *subname)
737 char *name;
738 tree_entry *current, *base;
739 int flag = 1, len;
740 if (!ts.loaded)
741 return;
743 if (ts.check_name == NULL)
744 return;
746 /* Calculate the full name of the subdirectory */
747 if (subname[0] == '.' && (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
748 return;
749 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
750 name = g_strconcat (PATH_SEP_STR, subname, (char *) NULL);
751 else
752 name = concat_dir_and_file (ts.check_name, subname);
754 /* Search for the subdirectory */
755 current = ts.check_start;
756 while (current && (flag = pathcmp (current->name, name)) < 0)
757 current = current->next;
759 if (flag != 0)
761 /* Doesn't exist -> add it */
762 current = tree_store_add_entry (name);
763 ts.add_queue = g_list_prepend (ts.add_queue, g_strdup (name));
765 g_free (name);
767 /* Clear the deletion mark from the subdirectory and its children */
768 base = current;
769 if (base)
771 len = strlen (base->name);
772 base->mark = 0;
773 current = base->next;
774 while (current
775 && strncmp (current->name, base->name, len) == 0
776 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
778 current->mark = 0;
779 current = current->next;
784 /* --------------------------------------------------------------------------------------------- */
785 /** Mark the subdirectories of the current directory for delete */
787 tree_entry *
788 tree_store_start_check (const char *path)
790 tree_entry *current, *retval;
791 int len;
793 if (!ts.loaded)
794 return NULL;
796 g_return_val_if_fail (ts.check_name == NULL, NULL);
797 ts.check_start = NULL;
799 /* Search for the start of subdirectories */
800 current = tree_store_whereis (path);
801 if (!current)
803 struct stat s;
805 if (mc_stat (path, &s) == -1)
806 return NULL;
808 if (!S_ISDIR (s.st_mode))
809 return NULL;
811 current = tree_store_add_entry (path);
812 ts.check_name = g_strdup (path);
814 return current;
817 ts.check_name = g_strdup (path);
819 retval = current;
821 /* Mark old subdirectories for delete */
822 ts.check_start = current->next;
823 len = strlen (ts.check_name);
825 current = ts.check_start;
826 while (current
827 && strncmp (current->name, ts.check_name, len) == 0
828 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
830 current->mark = 1;
831 current = current->next;
834 return retval;
837 /* --------------------------------------------------------------------------------------------- */
838 /** Delete subdirectories which still have the deletion mark */
840 void
841 tree_store_end_check (void)
843 tree_entry *current, *old;
844 size_t len;
845 GList *the_queue;
847 if (!ts.loaded)
848 return;
850 g_return_if_fail (ts.check_name != NULL);
852 /* Check delete marks and delete if found */
853 len = strlen (ts.check_name);
855 current = ts.check_start;
856 while (current
857 && strncmp (current->name, ts.check_name, len) == 0
858 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
860 old = current;
861 current = current->next;
862 if (old->mark)
863 remove_entry (old);
866 /* get the stuff in the scan order */
867 ts.add_queue = g_list_reverse (ts.add_queue);
868 the_queue = ts.add_queue;
869 ts.add_queue = NULL;
870 g_free (ts.check_name);
871 ts.check_name = NULL;
873 g_list_foreach (the_queue, (GFunc) g_free, NULL);
874 g_list_free (the_queue);
877 /* --------------------------------------------------------------------------------------------- */
879 tree_entry *
880 tree_store_rescan (const char *dir)
882 DIR *dirp;
883 struct dirent *dp;
884 struct stat buf;
885 tree_entry *entry;
887 if (should_skip_directory (dir))
889 entry = tree_store_add_entry (dir);
890 entry->scanned = 1;
892 return entry;
895 entry = tree_store_start_check (dir);
897 if (!entry)
898 return NULL;
900 dirp = mc_opendir (dir);
901 if (dirp)
903 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
905 char *full_name;
907 if (dp->d_name[0] == '.')
909 if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
910 continue;
913 full_name = concat_dir_and_file (dir, dp->d_name);
914 if (mc_lstat (full_name, &buf) != -1)
916 if (S_ISDIR (buf.st_mode))
917 tree_store_mark_checked (dp->d_name);
919 g_free (full_name);
921 mc_closedir (dirp);
923 tree_store_end_check ();
924 entry->scanned = 1;
926 return entry;
929 /* --------------------------------------------------------------------------------------------- */