Ticket 1551: Update GPL version from 2 to 3
[midnight-commander.git] / src / filemanager / treestore.c
blobff912229b951691bf279d4786b1c24449f347ac3
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 tree_store_add_entry (PATH_SEP_STR);
284 tree_store_rescan (PATH_SEP_STR);
285 ts.loaded = TRUE;
288 return TRUE;
291 /* --------------------------------------------------------------------------------------------- */
293 static char *
294 encode (const char *string)
296 int special_chars;
297 const char *p;
298 char *q;
299 char *res;
301 for (special_chars = 0, p = string; *p; p++)
303 if (*p == '\n' || *p == '\\')
304 special_chars++;
307 res = g_malloc (p - string + special_chars + 1);
308 for (p = string, q = res; *p; p++, q++)
310 if (*p != '\n' && *p != '\\')
312 *q = *p;
313 continue;
316 *q++ = '\\';
318 switch (*p)
320 case '\n':
321 *q = 'n';
322 break;
324 case '\\':
325 *q = '\\';
326 break;
329 *q = 0;
330 return res;
333 /* --------------------------------------------------------------------------------------------- */
334 /** Saves the tree to the specified filename */
336 static int
337 tree_store_save_to (char *name)
339 tree_entry *current;
340 FILE *file;
342 file = fopen (name, "w");
343 if (!file)
344 return errno;
346 fprintf (file, "%s\n", TREE_SIGNATURE);
348 current = ts.tree_first;
349 while (current)
351 int i, common;
352 vfs_path_t *vpath = vfs_path_from_str (current->name);
354 if (vfs_file_is_local (vpath))
356 /* Clear-text compression */
357 if (current->prev && (common = str_common (current->prev->name, current->name)) > 2)
359 char *encoded = encode (current->name + common);
361 i = fprintf (file, "%d:%d %s\n", current->scanned, common, encoded);
362 g_free (encoded);
364 else
366 char *encoded = encode (current->name);
368 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
369 g_free (encoded);
372 if (i == EOF)
374 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
375 name, unix_error_string (errno));
376 vfs_path_free (vpath);
377 break;
380 vfs_path_free (vpath);
381 current = current->next;
383 tree_store_dirty (FALSE);
384 fclose (file);
386 return 0;
389 /* --------------------------------------------------------------------------------------------- */
391 static tree_entry *
392 tree_store_add_entry (const char *name)
394 int flag = -1;
395 tree_entry *current = ts.tree_first;
396 tree_entry *old = NULL;
397 tree_entry *new;
398 int i, len;
399 int submask = 0;
401 if (ts.tree_last && ts.tree_last->next)
402 abort ();
404 /* Search for the correct place */
405 while (current && (flag = pathcmp (current->name, name)) < 0)
407 old = current;
408 current = current->next;
411 if (flag == 0)
412 return current; /* Already in the list */
414 /* Not in the list -> add it */
415 new = g_new0 (tree_entry, 1);
416 if (!current)
418 /* Append to the end of the list */
419 if (!ts.tree_first)
421 /* Empty list */
422 ts.tree_first = new;
423 new->prev = NULL;
425 else
427 old->next = new;
428 new->prev = old;
430 new->next = NULL;
431 ts.tree_last = new;
433 else
435 /* Insert in to the middle of the list */
436 new->prev = old;
437 if (old)
439 /* Yes, in the middle */
440 new->next = old->next;
441 old->next = new;
443 else
445 /* Nope, in the beginning of the list */
446 new->next = ts.tree_first;
447 ts.tree_first = new;
449 new->next->prev = new;
452 /* Calculate attributes */
453 new->name = g_strdup (name);
454 len = strlen (new->name);
455 new->sublevel = 0;
456 for (i = 0; i < len; i++)
457 if (new->name[i] == PATH_SEP)
459 new->sublevel++;
460 new->subname = new->name + i + 1;
462 if (new->next)
463 submask = new->next->submask;
464 else
465 submask = 0;
466 submask |= 1 << new->sublevel;
467 submask &= (2 << new->sublevel) - 1;
468 new->submask = submask;
469 new->mark = 0;
471 /* Correct the submasks of the previous entries */
472 current = new->prev;
473 while (current && current->sublevel > new->sublevel)
475 current->submask |= 1 << new->sublevel;
476 current = current->prev;
479 /* The entry has now been added */
481 if (new->sublevel > 1)
483 /* Let's check if the parent directory is in the tree */
484 char *parent = g_strdup (new->name);
486 for (i = strlen (parent) - 1; i > 1; i--)
488 if (parent[i] == PATH_SEP)
490 parent[i] = 0;
491 tree_store_add_entry (parent);
492 break;
495 g_free (parent);
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 char *dir)
590 static GList *special_dirs = NULL;
591 GList *l;
592 static gboolean loaded = FALSE;
594 if (!loaded)
596 loaded = TRUE;
597 setup_init ();
598 process_special_dirs (&special_dirs, profile_name);
599 process_special_dirs (&special_dirs, global_profile_name);
602 for (l = special_dirs; l != NULL; l = g_list_next (l))
603 if (strncmp (dir, l->data, strlen (l->data)) == 0)
604 return TRUE;
606 return FALSE;
609 /* --------------------------------------------------------------------------------------------- */
610 /*** public functions ****************************************************************************/
611 /* --------------------------------------------------------------------------------------------- */
613 /* Searches for specified directory */
614 tree_entry *
615 tree_store_whereis (const char *name)
617 tree_entry *current = ts.tree_first;
618 int flag = -1;
620 while (current && (flag = pathcmp (current->name, name)) < 0)
621 current = current->next;
623 if (flag == 0)
624 return current;
625 else
626 return NULL;
629 /* --------------------------------------------------------------------------------------------- */
631 struct TreeStore *
632 tree_store_get (void)
634 return &ts;
637 /* --------------------------------------------------------------------------------------------- */
639 * \fn int tree_store_load(void)
640 * \brief Loads the tree from the default location
641 * \return 1 if success (true), 0 otherwise (false)
645 tree_store_load (void)
647 char *name;
648 int retval;
650 name = g_build_filename (mc_config_get_cache_path (), MC_TREESTORE_FILE, NULL);
651 retval = tree_store_load_from (name);
652 g_free (name);
654 return retval;
657 /* --------------------------------------------------------------------------------------------- */
659 * \fn int tree_store_save(void)
660 * \brief Saves the tree to the default file in an atomic fashion
661 * \return 0 if success, errno on error
665 tree_store_save (void)
667 char *name;
668 int retval;
670 name = g_build_filename (mc_config_get_cache_path (), MC_TREESTORE_FILE, NULL);
671 mc_util_make_backup_if_possible (name, ".tmp");
673 retval = tree_store_save_to (name);
674 if (retval != 0)
676 mc_util_restore_from_backup_if_possible (name, ".tmp");
677 g_free (name);
678 return retval;
681 mc_util_unlink_backup_if_possible (name, ".tmp");
682 g_free (name);
683 return 0;
686 /* --------------------------------------------------------------------------------------------- */
688 void
689 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
691 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
694 /* --------------------------------------------------------------------------------------------- */
696 void
697 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
699 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
703 /* --------------------------------------------------------------------------------------------- */
705 void
706 tree_store_remove_entry (const char *name)
708 tree_entry *current, *base, *old;
709 int len;
711 g_return_if_fail (name != NULL);
713 /* Miguel Ugly hack */
714 if (name[0] == PATH_SEP && name[1] == 0)
715 return;
716 /* Miguel Ugly hack end */
718 base = tree_store_whereis (name);
719 if (!base)
720 return; /* Doesn't exist */
722 len = strlen (base->name);
723 current = base->next;
724 while (current
725 && strncmp (current->name, base->name, len) == 0
726 && (current->name[len] == '\0' || current->name[len] == PATH_SEP))
728 old = current;
729 current = current->next;
730 remove_entry (old);
732 remove_entry (base);
733 tree_store_dirty (TRUE);
735 return;
738 /* --------------------------------------------------------------------------------------------- */
739 /** This subdirectory exists -> clear deletion mark */
741 void
742 tree_store_mark_checked (const char *subname)
744 char *name;
745 tree_entry *current, *base;
746 int flag = 1, len;
747 if (!ts.loaded)
748 return;
750 if (ts.check_name == NULL)
751 return;
753 /* Calculate the full name of the subdirectory */
754 if (subname[0] == '.' && (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
755 return;
756 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
757 name = g_strconcat (PATH_SEP_STR, subname, (char *) NULL);
758 else
759 name = concat_dir_and_file (ts.check_name, subname);
761 /* Search for the subdirectory */
762 current = ts.check_start;
763 while (current && (flag = pathcmp (current->name, name)) < 0)
764 current = current->next;
766 if (flag != 0)
768 /* Doesn't exist -> add it */
769 current = tree_store_add_entry (name);
770 ts.add_queue = g_list_prepend (ts.add_queue, g_strdup (name));
772 g_free (name);
774 /* Clear the deletion mark from the subdirectory and its children */
775 base = current;
776 if (base)
778 len = strlen (base->name);
779 base->mark = 0;
780 current = base->next;
781 while (current
782 && strncmp (current->name, base->name, len) == 0
783 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
785 current->mark = 0;
786 current = current->next;
791 /* --------------------------------------------------------------------------------------------- */
792 /** Mark the subdirectories of the current directory for delete */
794 tree_entry *
795 tree_store_start_check (const char *path)
797 tree_entry *current, *retval;
798 int len;
800 if (!ts.loaded)
801 return NULL;
803 g_return_val_if_fail (ts.check_name == NULL, NULL);
804 ts.check_start = NULL;
806 /* Search for the start of subdirectories */
807 current = tree_store_whereis (path);
808 if (!current)
810 struct stat s;
812 if (mc_stat (path, &s) == -1)
813 return NULL;
815 if (!S_ISDIR (s.st_mode))
816 return NULL;
818 current = tree_store_add_entry (path);
819 ts.check_name = g_strdup (path);
821 return current;
824 ts.check_name = g_strdup (path);
826 retval = current;
828 /* Mark old subdirectories for delete */
829 ts.check_start = current->next;
830 len = strlen (ts.check_name);
832 current = ts.check_start;
833 while (current
834 && strncmp (current->name, ts.check_name, len) == 0
835 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
837 current->mark = 1;
838 current = current->next;
841 return retval;
844 /* --------------------------------------------------------------------------------------------- */
845 /** Delete subdirectories which still have the deletion mark */
847 void
848 tree_store_end_check (void)
850 tree_entry *current, *old;
851 size_t len;
852 GList *the_queue;
854 if (!ts.loaded)
855 return;
857 g_return_if_fail (ts.check_name != NULL);
859 /* Check delete marks and delete if found */
860 len = strlen (ts.check_name);
862 current = ts.check_start;
863 while (current
864 && strncmp (current->name, ts.check_name, len) == 0
865 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
867 old = current;
868 current = current->next;
869 if (old->mark)
870 remove_entry (old);
873 /* get the stuff in the scan order */
874 ts.add_queue = g_list_reverse (ts.add_queue);
875 the_queue = ts.add_queue;
876 ts.add_queue = NULL;
877 g_free (ts.check_name);
878 ts.check_name = NULL;
880 g_list_foreach (the_queue, (GFunc) g_free, NULL);
881 g_list_free (the_queue);
884 /* --------------------------------------------------------------------------------------------- */
886 tree_entry *
887 tree_store_rescan (const char *dir)
889 DIR *dirp;
890 struct dirent *dp;
891 struct stat buf;
892 tree_entry *entry;
894 if (should_skip_directory (dir))
896 entry = tree_store_add_entry (dir);
897 entry->scanned = 1;
899 return entry;
902 entry = tree_store_start_check (dir);
904 if (!entry)
905 return NULL;
907 dirp = mc_opendir (dir);
908 if (dirp)
910 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
912 char *full_name;
914 if (dp->d_name[0] == '.')
916 if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
917 continue;
920 full_name = concat_dir_and_file (dir, dp->d_name);
921 if (mc_lstat (full_name, &buf) != -1)
923 if (S_ISDIR (buf.st_mode))
924 tree_store_mark_checked (dp->d_name);
926 g_free (full_name);
928 mc_closedir (dirp);
930 tree_store_end_check ();
931 entry->scanned = 1;
933 return entry;
936 /* --------------------------------------------------------------------------------------------- */