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-2024
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/strutil.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 /*** forward declarations (file scope functions) *************************************************/
75 static tree_entry
*tree_store_add_entry (const vfs_path_t
* name
);
77 /*** file scope variables ************************************************************************/
79 static struct TreeStore ts
;
81 static hook_t
*remove_entry_hooks
;
83 /* --------------------------------------------------------------------------------------------- */
84 /*** file scope functions ************************************************************************/
85 /* --------------------------------------------------------------------------------------------- */
88 tree_store_dirty (gboolean dirty
)
93 /* --------------------------------------------------------------------------------------------- */
96 * @return the number of common bytes in the strings.
100 str_common (const vfs_path_t
*s1_vpath
, const vfs_path_t
*s2_vpath
)
105 s1
= vfs_path_as_str (s1_vpath
);
106 s2
= vfs_path_as_str (s2_vpath
);
108 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
114 /* --------------------------------------------------------------------------------------------- */
115 /** The directory names are arranged in a single linked list in the same
116 * order as they are displayed. When the tree is displayed the expected
117 * order is like this:
127 * i.e. the required collating sequence when comparing two directory names is
128 * '\0' < PATH_SEP < all-other-characters-in-encoding-order
130 * Since strcmp doesn't fulfil this requirement we use pathcmp when
131 * inserting directory names into the list. The meaning of the return value
132 * of pathcmp and strcmp are the same (an integer less than, equal to, or
133 * greater than zero if p1 is found to be less than, to match, or be greater
138 pathcmp (const vfs_path_t
*p1_vpath
, const vfs_path_t
*p2_vpath
)
143 p1
= vfs_path_as_str (p1_vpath
);
144 p2
= vfs_path_as_str (p2_vpath
);
146 for (; *p1
== *p2
; p1
++, p2
++)
152 else if (*p2
== '\0')
154 else if (IS_PATH_SEP (*p1
))
156 else if (IS_PATH_SEP (*p2
))
159 ret_val
= (*p1
- *p2
);
164 /* --------------------------------------------------------------------------------------------- */
167 decode (char *buffer
)
171 res
= g_strdup (buffer
);
173 for (p
= q
= res
; *p
!= '\0'; p
++, q
++)
207 /* --------------------------------------------------------------------------------------------- */
208 /** Loads the tree store from the specified filename */
211 tree_store_load_from (const char *name
)
214 char buffer
[MC_MAXPATHLEN
+ 20];
216 g_return_val_if_fail (name
!= NULL
, 0);
221 file
= fopen (name
, "r");
224 && (fgets (buffer
, sizeof (buffer
), file
) == NULL
225 || strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0))
233 char oldname
[MC_MAXPATHLEN
] = "\0";
237 /* File open -> read contents */
238 while (fgets (buffer
, MC_MAXPATHLEN
, file
))
244 /* Skip invalid records */
245 if (buffer
[0] != '0' && buffer
[0] != '1')
248 if (buffer
[1] != ':')
251 scanned
= buffer
[0] == '1';
253 lc_name
= decode (buffer
+ 2);
254 if (!IS_PATH_SEP (lc_name
[0]))
256 /* Clear-text decompression */
259 s
= strtok (lc_name
, " ");
266 different
= strtok (NULL
, "");
267 if (different
!= NULL
)
271 vpath
= vfs_path_from_str (oldname
);
272 g_strlcpy (oldname
+ common
, different
, sizeof (oldname
) - (size_t) common
);
273 if (vfs_file_is_local (vpath
))
275 vfs_path_t
*tmp_vpath
;
277 tmp_vpath
= vfs_path_from_str (oldname
);
278 e
= tree_store_add_entry (tmp_vpath
);
279 vfs_path_free (tmp_vpath
, TRUE
);
280 e
->scanned
= scanned
;
282 vfs_path_free (vpath
, TRUE
);
290 vpath
= vfs_path_from_str (lc_name
);
291 if (vfs_file_is_local (vpath
))
293 e
= tree_store_add_entry (vpath
);
294 e
->scanned
= scanned
;
296 vfs_path_free (vpath
, TRUE
);
297 g_strlcpy (oldname
, lc_name
, sizeof (oldname
));
305 /* Nothing loaded, we add some standard directories */
308 vfs_path_t
*tmp_vpath
;
310 tmp_vpath
= vfs_path_from_str (PATH_SEP_STR
);
311 tree_store_add_entry (tmp_vpath
);
312 tree_store_rescan (tmp_vpath
);
313 vfs_path_free (tmp_vpath
, TRUE
);
320 /* --------------------------------------------------------------------------------------------- */
323 encode (const vfs_path_t
*vpath
, size_t offset
)
325 return str_escape (vfs_path_as_str (vpath
) + offset
, -1, "\n\\", FALSE
);
328 /* --------------------------------------------------------------------------------------------- */
329 /** Saves the tree to the specified filename */
332 tree_store_save_to (char *name
)
337 file
= fopen (name
, "w");
341 fprintf (file
, "%s\n", TREE_SIGNATURE
);
343 for (current
= ts
.tree_first
; current
!= NULL
; current
= current
->next
)
344 if (vfs_file_is_local (current
->name
))
348 /* Clear-text compression */
349 if (current
->prev
!= NULL
350 && (common
= str_common (current
->prev
->name
, current
->name
)) > 2)
354 encoded
= encode (current
->name
, common
);
355 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
? 1 : 0, common
, encoded
);
362 encoded
= encode (current
->name
, 0);
363 i
= fprintf (file
, "%d:%s\n", current
->scanned
? 1 : 0, encoded
);
369 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"),
370 name
, unix_error_string (errno
));
375 tree_store_dirty (FALSE
);
381 /* --------------------------------------------------------------------------------------------- */
384 tree_store_add_entry (const vfs_path_t
*name
)
388 tree_entry
*old
= NULL
;
392 if (ts
.tree_last
!= NULL
&& ts
.tree_last
->next
!= NULL
)
395 /* Search for the correct place */
396 for (current
= ts
.tree_first
;
397 current
!= NULL
&& (flag
= pathcmp (current
->name
, name
)) < 0; current
= current
->next
)
401 return current
; /* Already in the list */
403 /* Not in the list -> add it */
404 new = g_new0 (tree_entry
, 1);
407 /* Append to the end of the list */
408 if (ts
.tree_first
== NULL
)
425 /* Insert in to the middle of the list */
429 /* Yes, in the middle */
430 new->next
= old
->next
;
435 /* Nope, in the beginning of the list */
436 new->next
= ts
.tree_first
;
439 new->next
->prev
= new;
442 /* Calculate attributes */
443 new->name
= vfs_path_clone (name
);
444 new->sublevel
= vfs_path_tokens_count (new->name
);
447 const char *new_name
;
449 new_name
= vfs_path_get_last_path_str (new->name
);
450 new->subname
= strrchr (new_name
, PATH_SEP
);
451 if (new->subname
== NULL
)
452 new->subname
= new_name
;
457 if (new->next
!= NULL
)
458 submask
= new->next
->submask
;
460 submask
|= 1 << new->sublevel
;
461 submask
&= (2 << new->sublevel
) - 1;
462 new->submask
= submask
;
465 /* Correct the submasks of the previous entries */
466 for (current
= new->prev
;
467 current
!= NULL
&& current
->sublevel
> new->sublevel
; current
= current
->prev
)
468 current
->submask
|= 1 << new->sublevel
;
470 tree_store_dirty (TRUE
);
474 /* --------------------------------------------------------------------------------------------- */
477 tree_store_notify_remove (tree_entry
*entry
)
481 for (p
= remove_entry_hooks
; p
!= NULL
; p
= p
->next
)
483 tree_store_remove_fn r
= (tree_store_remove_fn
) p
->hook_fn
;
485 r (entry
, p
->hook_data
);
489 /* --------------------------------------------------------------------------------------------- */
492 remove_entry (tree_entry
*entry
)
494 tree_entry
*current
= entry
->prev
;
496 tree_entry
*ret
= NULL
;
498 tree_store_notify_remove (entry
);
500 /* Correct the submasks of the previous entries */
501 if (entry
->next
!= NULL
)
502 submask
= entry
->next
->submask
;
504 for (; current
!= NULL
&& current
->sublevel
> entry
->sublevel
; current
= current
->prev
)
506 submask
|= 1 << current
->sublevel
;
507 submask
&= (2 << current
->sublevel
) - 1;
508 current
->submask
= submask
;
511 /* Unlink the entry from the list */
512 if (entry
->prev
!= NULL
)
513 entry
->prev
->next
= entry
->next
;
515 ts
.tree_first
= entry
->next
;
517 if (entry
->next
!= NULL
)
518 entry
->next
->prev
= entry
->prev
;
520 ts
.tree_last
= entry
->prev
;
522 /* Free the memory used by the entry */
523 vfs_path_free (entry
->name
, TRUE
);
529 /* --------------------------------------------------------------------------------------------- */
532 process_special_dirs (GList
**special_dirs
, const char *file
)
537 cfg
= mc_config_init (file
, TRUE
);
541 start_buff
= mc_config_get_string_list (cfg
, "Special dirs", "list", NULL
);
542 if (start_buff
!= NULL
)
546 for (buffers
= start_buff
; *buffers
!= NULL
; buffers
++)
548 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
552 g_strfreev (start_buff
);
554 mc_config_deinit (cfg
);
557 /* --------------------------------------------------------------------------------------------- */
560 should_skip_directory (const vfs_path_t
*vpath
)
562 static GList
*special_dirs
= NULL
;
564 static gboolean loaded
= FALSE
;
565 gboolean ret
= FALSE
;
569 const char *profile_name
;
571 profile_name
= setup_init ();
572 process_special_dirs (&special_dirs
, profile_name
);
573 process_special_dirs (&special_dirs
, mc_global
.profile_name
);
578 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
579 if (strncmp (vfs_path_as_str (vpath
), l
->data
, strlen (l
->data
)) == 0)
588 /* --------------------------------------------------------------------------------------------- */
591 queue_vpath_free (gpointer data
)
593 vfs_path_free ((vfs_path_t
*) data
, TRUE
);
596 /* --------------------------------------------------------------------------------------------- */
597 /*** public functions ****************************************************************************/
598 /* --------------------------------------------------------------------------------------------- */
600 /* Searches for specified directory */
602 tree_store_whereis (const vfs_path_t
*name
)
607 for (current
= ts
.tree_first
;
608 current
!= NULL
&& (flag
= pathcmp (current
->name
, name
)) < 0; current
= current
->next
)
611 return flag
== 0 ? current
: NULL
;
614 /* --------------------------------------------------------------------------------------------- */
617 tree_store_get (void)
622 /* --------------------------------------------------------------------------------------------- */
624 * \fn int tree_store_load(void)
625 * \brief Loads the tree from the default location
626 * \return 1 if success (true), 0 otherwise (false)
630 tree_store_load (void)
635 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
636 retval
= tree_store_load_from (name
);
642 /* --------------------------------------------------------------------------------------------- */
644 * \fn int tree_store_save(void)
645 * \brief Saves the tree to the default file in an atomic fashion
646 * \return 0 if success, errno on error
650 tree_store_save (void)
655 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
656 mc_util_make_backup_if_possible (name
, ".tmp");
658 retval
= tree_store_save_to (name
);
660 mc_util_restore_from_backup_if_possible (name
, ".tmp");
662 mc_util_unlink_backup_if_possible (name
, ".tmp");
668 /* --------------------------------------------------------------------------------------------- */
671 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
673 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
676 /* --------------------------------------------------------------------------------------------- */
679 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
681 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
684 /* --------------------------------------------------------------------------------------------- */
687 tree_store_remove_entry (const vfs_path_t
*name_vpath
)
689 tree_entry
*current
, *base
;
692 g_return_if_fail (name_vpath
!= NULL
);
694 /* Miguel Ugly hack */
697 const char *name_vpath_str
;
699 name_vpath_str
= vfs_path_as_str (name_vpath
);
700 is_root
= (IS_PATH_SEP (name_vpath_str
[0]) && name_vpath_str
[1] == '\0');
704 /* Miguel Ugly hack end */
706 base
= tree_store_whereis (name_vpath
);
708 return; /* Doesn't exist */
710 len
= vfs_path_len (base
->name
);
711 current
= base
->next
;
712 while (current
!= NULL
&& vfs_path_equal_len (current
->name
, base
->name
, len
))
718 cname
= vfs_path_as_str (current
->name
);
719 ok
= (cname
[len
] == '\0' || IS_PATH_SEP (cname
[len
]));
724 current
= current
->next
;
728 tree_store_dirty (TRUE
);
731 /* --------------------------------------------------------------------------------------------- */
732 /** This subdirectory exists -> clear deletion mark */
735 tree_store_mark_checked (const char *subname
)
738 tree_entry
*current
, *base
;
745 if (ts
.check_name
== NULL
)
748 /* Calculate the full name of the subdirectory */
749 if (DIR_IS_DOT (subname
) || DIR_IS_DOTDOT (subname
))
752 cname
= vfs_path_as_str (ts
.check_name
);
753 if (IS_PATH_SEP (cname
[0]) && cname
[1] == '\0')
754 name
= vfs_path_build_filename (PATH_SEP_STR
, subname
, (char *) NULL
);
756 name
= vfs_path_append_new (ts
.check_name
, subname
, (char *) NULL
);
758 /* Search for the subdirectory */
759 for (current
= ts
.check_start
;
760 current
!= NULL
&& (flag
= pathcmp (current
->name
, name
)) < 0; current
= current
->next
)
765 /* Doesn't exist -> add it */
766 current
= tree_store_add_entry (name
);
767 ts
.add_queue_vpath
= g_list_prepend (ts
.add_queue_vpath
, name
);
770 vfs_path_free (name
, TRUE
);
772 /* Clear the deletion mark from the subdirectory and its children */
778 len
= vfs_path_len (base
->name
);
780 for (current
= base
->next
;
781 current
!= NULL
&& vfs_path_equal_len (current
->name
, base
->name
, len
);
782 current
= current
->next
)
786 cname
= vfs_path_as_str (current
->name
);
787 ok
= (cname
[len
] == '\0' || IS_PATH_SEP (cname
[len
]) || len
== 1);
791 current
->mark
= FALSE
;
796 /* --------------------------------------------------------------------------------------------- */
797 /** Mark the subdirectories of the current directory for delete */
800 tree_store_start_check (const vfs_path_t
*vpath
)
802 tree_entry
*current
, *retval
;
808 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
809 ts
.check_start
= NULL
;
811 /* Search for the start of subdirectories */
812 current
= tree_store_whereis (vpath
);
817 if (mc_stat (vpath
, &s
) == -1 || !S_ISDIR (s
.st_mode
))
820 current
= tree_store_add_entry (vpath
);
821 ts
.check_name
= vfs_path_clone (vpath
);
826 ts
.check_name
= vfs_path_clone (vpath
);
830 /* Mark old subdirectories for delete */
831 ts
.check_start
= current
->next
;
832 len
= vfs_path_len (ts
.check_name
);
834 for (current
= ts
.check_start
;
835 current
!= NULL
&& vfs_path_equal_len (current
->name
, ts
.check_name
, len
);
836 current
= current
->next
)
841 cname
= vfs_path_as_str (current
->name
);
842 ok
= (cname
[len
] == '\0' || IS_PATH_SEP (cname
[len
]) || len
== 1);
846 current
->mark
= TRUE
;
852 /* --------------------------------------------------------------------------------------------- */
853 /** Delete subdirectories which still have the deletion mark */
856 tree_store_end_check (void)
865 g_return_if_fail (ts
.check_name
!= NULL
);
867 /* Check delete marks and delete if found */
868 len
= vfs_path_len (ts
.check_name
);
870 current
= ts
.check_start
;
871 while (current
!= NULL
&& vfs_path_equal_len (current
->name
, ts
.check_name
, len
))
877 cname
= vfs_path_as_str (current
->name
);
878 ok
= (cname
[len
] == '\0' || IS_PATH_SEP (cname
[len
]) || len
== 1);
883 current
= current
->next
;
888 /* get the stuff in the scan order */
889 the_queue
= g_list_reverse (ts
.add_queue_vpath
);
890 ts
.add_queue_vpath
= NULL
;
891 vfs_path_free (ts
.check_name
, TRUE
);
892 ts
.check_name
= NULL
;
894 g_list_free_full (the_queue
, queue_vpath_free
);
897 /* --------------------------------------------------------------------------------------------- */
900 tree_store_rescan (const vfs_path_t
*vpath
)
906 if (should_skip_directory (vpath
))
908 entry
= tree_store_add_entry (vpath
);
909 entry
->scanned
= TRUE
;
913 entry
= tree_store_start_check (vpath
);
917 dirp
= mc_opendir (vpath
);
920 struct vfs_dirent
*dp
;
922 for (dp
= mc_readdir (dirp
); dp
!= NULL
; dp
= mc_readdir (dirp
))
923 if (!DIR_IS_DOT (dp
->d_name
) && !DIR_IS_DOTDOT (dp
->d_name
))
925 vfs_path_t
*tmp_vpath
;
927 tmp_vpath
= vfs_path_append_new (vpath
, dp
->d_name
, (char *) NULL
);
928 if (mc_lstat (tmp_vpath
, &buf
) != -1 && S_ISDIR (buf
.st_mode
))
929 tree_store_mark_checked (dp
->d_name
);
930 vfs_path_free (tmp_vpath
, TRUE
);
935 tree_store_end_check ();
936 entry
->scanned
= TRUE
;
941 /* --------------------------------------------------------------------------------------------- */