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-2016
12 Free Software Foundation, Inc.
15 Janne Kukonlehto, 1994, 1996
17 Miguel de Icaza, 1996, 1999
18 Slava Zanko <slavazanko@gmail.com>, 2013
19 Andrew Borodin <aborodin@vmail.ru>, 2013
21 This file is part of the Midnight Commander.
23 The Midnight Commander is free software: you can redistribute it
24 and/or modify it under the terms of the GNU General Public License as
25 published by the Free Software Foundation, either version 3 of the License,
26 or (at your option) any later version.
28 The Midnight Commander is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU General Public License for more details.
33 You should have received a copy of the GNU General Public License
34 along with this program. If not, see <http://www.gnu.org/licenses/>.
38 * \brief Source: tree store
40 * Contains a storage of the file system tree representation.
49 #include <sys/types.h>
53 #include "lib/global.h"
54 #include "lib/mcconfig.h"
55 #include "lib/vfs/vfs.h"
56 #include "lib/fileloc.h"
57 #include "lib/strescape.h"
61 #include "src/setup.h" /* setup_init() */
63 #include "treestore.h"
65 /*** global variables ****************************************************************************/
67 /*** file scope macro definitions ****************************************************************/
69 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
71 /*** file scope type declarations ****************************************************************/
73 /*** file scope variables ************************************************************************/
75 static struct TreeStore ts
;
77 static hook_t
*remove_entry_hooks
;
79 /*** file scope functions ************************************************************************/
80 /* --------------------------------------------------------------------------------------------- */
82 static tree_entry
*tree_store_add_entry (const vfs_path_t
* name
);
84 /* --------------------------------------------------------------------------------------------- */
87 tree_store_dirty (gboolean dirty
)
92 /* --------------------------------------------------------------------------------------------- */
95 * @return the number of common bytes in the strings.
99 str_common (const vfs_path_t
* s1_vpath
, const vfs_path_t
* s2_vpath
)
104 s1
= vfs_path_as_str (s1_vpath
);
105 s2
= vfs_path_as_str (s2_vpath
);
107 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
113 /* --------------------------------------------------------------------------------------------- */
114 /** The directory names are arranged in a single linked list in the same
115 * order as they are displayed. When the tree is displayed the expected
116 * order is like this:
126 * i.e. the required collating sequence when comparing two directory names is
127 * '\0' < PATH_SEP < all-other-characters-in-encoding-order
129 * Since strcmp doesn't fulfil this requirement we use pathcmp when
130 * inserting directory names into the list. The meaning of the return value
131 * of pathcmp and strcmp are the same (an integer less than, equal to, or
132 * greater than zero if p1 is found to be less than, to match, or be greater
137 pathcmp (const vfs_path_t
* p1_vpath
, const vfs_path_t
* p2_vpath
)
142 p1
= vfs_path_as_str (p1_vpath
);
143 p2
= vfs_path_as_str (p2_vpath
);
145 for (; *p1
== *p2
; p1
++, p2
++)
151 else if (*p2
== '\0')
153 else if (IS_PATH_SEP (*p1
))
155 else if (IS_PATH_SEP (*p2
))
158 ret_val
= (*p1
- *p2
);
163 /* --------------------------------------------------------------------------------------------- */
166 decode (char *buffer
)
168 char *res
= g_strdup (buffer
);
171 for (p
= q
= res
; *p
; p
++, q
++)
204 /* --------------------------------------------------------------------------------------------- */
205 /** Loads the tree store from the specified filename */
208 tree_store_load_from (char *name
)
211 char buffer
[MC_MAXPATHLEN
+ 20];
213 g_return_val_if_fail (name
!= NULL
, 0);
218 file
= fopen (name
, "r");
221 && (fgets (buffer
, sizeof (buffer
), file
) == NULL
222 || strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0))
230 char oldname
[MC_MAXPATHLEN
] = "\0";
234 /* File open -> read contents */
235 while (fgets (buffer
, MC_MAXPATHLEN
, file
))
241 /* Skip invalid records */
242 if ((buffer
[0] != '0' && buffer
[0] != '1'))
245 if (buffer
[1] != ':')
248 scanned
= buffer
[0] == '1';
250 lc_name
= decode (buffer
+ 2);
251 if (!IS_PATH_SEP (lc_name
[0]))
253 /* Clear-text decompression */
254 char *s
= strtok (lc_name
, " ");
262 different
= strtok (NULL
, "");
267 vpath
= vfs_path_from_str (oldname
);
268 strcpy (oldname
+ common
, different
);
269 if (vfs_file_is_local (vpath
))
271 vfs_path_t
*tmp_vpath
;
273 tmp_vpath
= vfs_path_from_str (oldname
);
274 e
= tree_store_add_entry (tmp_vpath
);
275 vfs_path_free (tmp_vpath
);
276 e
->scanned
= scanned
;
278 vfs_path_free (vpath
);
286 vpath
= vfs_path_from_str (lc_name
);
287 if (vfs_file_is_local (vpath
))
289 e
= tree_store_add_entry (vpath
);
290 e
->scanned
= scanned
;
292 vfs_path_free (vpath
);
293 strcpy (oldname
, lc_name
);
301 /* Nothing loaded, we add some standard directories */
304 vfs_path_t
*tmp_vpath
= vfs_path_from_str (PATH_SEP_STR
);
305 tree_store_add_entry (tmp_vpath
);
306 tree_store_rescan (tmp_vpath
);
307 vfs_path_free (tmp_vpath
);
314 /* --------------------------------------------------------------------------------------------- */
317 encode (const vfs_path_t
* vpath
, size_t offset
)
319 return strutils_escape (vfs_path_as_str (vpath
) + offset
, -1, "\n\\", FALSE
);
322 /* --------------------------------------------------------------------------------------------- */
323 /** Saves the tree to the specified filename */
326 tree_store_save_to (char *name
)
331 file
= fopen (name
, "w");
335 fprintf (file
, "%s\n", TREE_SIGNATURE
);
337 current
= ts
.tree_first
;
340 if (vfs_file_is_local (current
->name
))
344 /* Clear-text compression */
345 if (current
->prev
&& (common
= str_common (current
->prev
->name
, current
->name
)) > 2)
347 char *encoded
= encode (current
->name
, common
);
349 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
? 1 : 0, common
, encoded
);
354 char *encoded
= encode (current
->name
, 0);
356 i
= fprintf (file
, "%d:%s\n", current
->scanned
? 1 : 0, encoded
);
362 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"),
363 name
, unix_error_string (errno
));
367 current
= current
->next
;
369 tree_store_dirty (FALSE
);
375 /* --------------------------------------------------------------------------------------------- */
378 tree_store_add_entry (const vfs_path_t
* name
)
381 tree_entry
*current
= ts
.tree_first
;
382 tree_entry
*old
= NULL
;
386 if (ts
.tree_last
&& ts
.tree_last
->next
)
389 /* Search for the correct place */
390 while (current
!= NULL
&& (flag
= pathcmp (current
->name
, name
)) < 0)
393 current
= current
->next
;
397 return current
; /* Already in the list */
399 /* Not in the list -> add it */
400 new = g_new0 (tree_entry
, 1);
403 /* Append to the end of the list */
421 /* Insert in to the middle of the list */
425 /* Yes, in the middle */
426 new->next
= old
->next
;
431 /* Nope, in the beginning of the list */
432 new->next
= ts
.tree_first
;
435 new->next
->prev
= new;
438 /* Calculate attributes */
439 new->name
= vfs_path_clone (name
);
440 new->sublevel
= vfs_path_tokens_count (new->name
);
442 const char *new_name
;
444 new_name
= vfs_path_get_last_path_str (new->name
);
445 new->subname
= strrchr (new_name
, PATH_SEP
);
446 if (new->subname
== NULL
)
447 new->subname
= new_name
;
452 submask
= new->next
->submask
;
455 submask
|= 1 << new->sublevel
;
456 submask
&= (2 << new->sublevel
) - 1;
457 new->submask
= submask
;
460 /* Correct the submasks of the previous entries */
462 while (current
&& current
->sublevel
> new->sublevel
)
464 current
->submask
|= 1 << new->sublevel
;
465 current
= current
->prev
;
468 tree_store_dirty (TRUE
);
472 /* --------------------------------------------------------------------------------------------- */
475 tree_store_notify_remove (tree_entry
* entry
)
477 hook_t
*p
= remove_entry_hooks
;
481 tree_store_remove_fn r
= (tree_store_remove_fn
) p
->hook_fn
;
483 r (entry
, p
->hook_data
);
488 /* --------------------------------------------------------------------------------------------- */
491 remove_entry (tree_entry
* entry
)
493 tree_entry
*current
= entry
->prev
;
495 tree_entry
*ret
= NULL
;
497 tree_store_notify_remove (entry
);
499 /* Correct the submasks of the previous entries */
501 submask
= entry
->next
->submask
;
502 while (current
&& current
->sublevel
> entry
->sublevel
)
504 submask
|= 1 << current
->sublevel
;
505 submask
&= (2 << current
->sublevel
) - 1;
506 current
->submask
= submask
;
507 current
= current
->prev
;
510 /* Unlink the entry from the list */
512 entry
->prev
->next
= entry
->next
;
514 ts
.tree_first
= entry
->next
;
517 entry
->next
->prev
= entry
->prev
;
519 ts
.tree_last
= entry
->prev
;
521 /* Free the memory used by the entry */
522 vfs_path_free (entry
->name
);
528 /* --------------------------------------------------------------------------------------------- */
531 process_special_dirs (GList
** special_dirs
, const char *file
)
536 cfg
= mc_config_init (file
, TRUE
);
540 start_buff
= mc_config_get_string_list (cfg
, "Special dirs", "list", NULL
);
541 if (start_buff
!= NULL
)
545 for (buffers
= start_buff
; *buffers
!= NULL
; buffers
++)
547 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
551 g_strfreev (start_buff
);
553 mc_config_deinit (cfg
);
556 /* --------------------------------------------------------------------------------------------- */
559 should_skip_directory (const vfs_path_t
* vpath
)
561 static GList
*special_dirs
= NULL
;
563 static gboolean loaded
= FALSE
;
564 gboolean ret
= FALSE
;
568 const char *profile_name
;
570 profile_name
= setup_init ();
571 process_special_dirs (&special_dirs
, profile_name
);
572 process_special_dirs (&special_dirs
, global_profile_name
);
577 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
578 if (strncmp (vfs_path_as_str (vpath
), l
->data
, strlen (l
->data
)) == 0)
587 /* --------------------------------------------------------------------------------------------- */
588 /*** public functions ****************************************************************************/
589 /* --------------------------------------------------------------------------------------------- */
591 /* Searches for specified directory */
593 tree_store_whereis (const vfs_path_t
* name
)
595 tree_entry
*current
= ts
.tree_first
;
598 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
599 current
= current
->next
;
607 /* --------------------------------------------------------------------------------------------- */
610 tree_store_get (void)
615 /* --------------------------------------------------------------------------------------------- */
617 * \fn int tree_store_load(void)
618 * \brief Loads the tree from the default location
619 * \return 1 if success (true), 0 otherwise (false)
623 tree_store_load (void)
628 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
629 retval
= tree_store_load_from (name
);
635 /* --------------------------------------------------------------------------------------------- */
637 * \fn int tree_store_save(void)
638 * \brief Saves the tree to the default file in an atomic fashion
639 * \return 0 if success, errno on error
643 tree_store_save (void)
648 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
649 mc_util_make_backup_if_possible (name
, ".tmp");
651 retval
= tree_store_save_to (name
);
653 mc_util_restore_from_backup_if_possible (name
, ".tmp");
655 mc_util_unlink_backup_if_possible (name
, ".tmp");
661 /* --------------------------------------------------------------------------------------------- */
664 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
666 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
669 /* --------------------------------------------------------------------------------------------- */
672 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
674 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
678 /* --------------------------------------------------------------------------------------------- */
681 tree_store_remove_entry (const vfs_path_t
* name_vpath
)
683 tree_entry
*current
, *base
;
686 g_return_if_fail (name_vpath
!= NULL
);
688 /* Miguel Ugly hack */
691 const char *name_vpath_str
;
693 name_vpath_str
= vfs_path_as_str (name_vpath
);
694 is_root
= (IS_PATH_SEP (name_vpath_str
[0]) && name_vpath_str
[1] == '\0');
698 /* Miguel Ugly hack end */
700 base
= tree_store_whereis (name_vpath
);
702 return; /* Doesn't exist */
704 len
= vfs_path_len (base
->name
);
705 current
= base
->next
;
706 while (current
!= NULL
&& vfs_path_equal_len (current
->name
, base
->name
, len
))
712 cname
= vfs_path_as_str (current
->name
);
713 ok
= (cname
[len
] == '\0' || IS_PATH_SEP (cname
[len
]));
718 current
= current
->next
;
722 tree_store_dirty (TRUE
);
725 /* --------------------------------------------------------------------------------------------- */
726 /** This subdirectory exists -> clear deletion mark */
729 tree_store_mark_checked (const char *subname
)
732 tree_entry
*current
, *base
;
739 if (ts
.check_name
== NULL
)
742 /* Calculate the full name of the subdirectory */
743 if (DIR_IS_DOT (subname
) || DIR_IS_DOTDOT (subname
))
746 cname
= vfs_path_as_str (ts
.check_name
);
747 if (IS_PATH_SEP (cname
[0]) && cname
[1] == '\0')
748 name
= vfs_path_build_filename (PATH_SEP_STR
, subname
, (char *) NULL
);
750 name
= vfs_path_append_new (ts
.check_name
, subname
, (char *) NULL
);
752 /* Search for the subdirectory */
753 current
= ts
.check_start
;
754 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
755 current
= current
->next
;
759 /* Doesn't exist -> add it */
760 current
= tree_store_add_entry (name
);
761 ts
.add_queue_vpath
= g_list_prepend (ts
.add_queue_vpath
, name
);
764 vfs_path_free (name
);
766 /* Clear the deletion mark from the subdirectory and its children */
772 len
= vfs_path_len (base
->name
);
774 current
= base
->next
;
775 while (current
!= NULL
&& vfs_path_equal_len (current
->name
, base
->name
, len
))
779 cname
= vfs_path_as_str (current
->name
);
780 ok
= (cname
[len
] == '\0' || IS_PATH_SEP (cname
[len
]) || len
== 1);
784 current
->mark
= FALSE
;
785 current
= current
->next
;
790 /* --------------------------------------------------------------------------------------------- */
791 /** Mark the subdirectories of the current directory for delete */
794 tree_store_start_check (const vfs_path_t
* vpath
)
796 tree_entry
*current
, *retval
;
802 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
803 ts
.check_start
= NULL
;
805 /* Search for the start of subdirectories */
806 current
= tree_store_whereis (vpath
);
811 if (mc_stat (vpath
, &s
) == -1)
814 if (!S_ISDIR (s
.st_mode
))
817 current
= tree_store_add_entry (vpath
);
818 ts
.check_name
= vfs_path_clone (vpath
);
823 ts
.check_name
= vfs_path_clone (vpath
);
827 /* Mark old subdirectories for delete */
828 ts
.check_start
= current
->next
;
829 len
= vfs_path_len (ts
.check_name
);
831 current
= ts
.check_start
;
832 while (current
!= NULL
&& vfs_path_equal_len (current
->name
, ts
.check_name
, len
))
837 cname
= vfs_path_as_str (current
->name
);
838 ok
= (cname
[len
] == '\0' || IS_PATH_SEP (cname
[len
]) || len
== 1);
842 current
->mark
= TRUE
;
843 current
= current
->next
;
849 /* --------------------------------------------------------------------------------------------- */
850 /** Delete subdirectories which still have the deletion mark */
853 tree_store_end_check (void)
862 g_return_if_fail (ts
.check_name
!= NULL
);
864 /* Check delete marks and delete if found */
865 len
= vfs_path_len (ts
.check_name
);
867 current
= ts
.check_start
;
868 while (current
!= NULL
&& vfs_path_equal_len (current
->name
, ts
.check_name
, len
))
874 cname
= vfs_path_as_str (current
->name
);
875 ok
= (cname
[len
] == '\0' || IS_PATH_SEP (cname
[len
]) || len
== 1);
880 current
= current
->next
;
885 /* get the stuff in the scan order */
886 the_queue
= g_list_reverse (ts
.add_queue_vpath
);
887 ts
.add_queue_vpath
= NULL
;
888 vfs_path_free (ts
.check_name
);
889 ts
.check_name
= NULL
;
891 g_list_free_full (the_queue
, (GDestroyNotify
) vfs_path_free
);
894 /* --------------------------------------------------------------------------------------------- */
897 tree_store_rescan (const vfs_path_t
* vpath
)
903 if (should_skip_directory (vpath
))
905 entry
= tree_store_add_entry (vpath
);
906 entry
->scanned
= TRUE
;
910 entry
= tree_store_start_check (vpath
);
914 dirp
= mc_opendir (vpath
);
919 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
921 vfs_path_t
*tmp_vpath
;
923 if (DIR_IS_DOT (dp
->d_name
) || DIR_IS_DOTDOT (dp
->d_name
))
926 tmp_vpath
= vfs_path_append_new (vpath
, dp
->d_name
, (char *) NULL
);
927 if (mc_lstat (tmp_vpath
, &buf
) != -1 && S_ISDIR (buf
.st_mode
))
928 tree_store_mark_checked (dp
->d_name
);
929 vfs_path_free (tmp_vpath
);
933 tree_store_end_check ();
934 entry
->scanned
= TRUE
;
939 /* --------------------------------------------------------------------------------------------- */