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,
13 The Free Software Foundation, Inc.
16 Janne Kukonlehto, 1994, 1996
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/>.
37 * \brief Source: tree store
39 * Contains a storage of the file system tree representation.
48 #include <sys/types.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"
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 /* --------------------------------------------------------------------------------------------- */
86 tree_store_dirty (int state
)
91 /* --------------------------------------------------------------------------------------------- */
94 * @return the number of common bytes in the strings.
98 str_common (const vfs_path_t
* s1_vpath
, const vfs_path_t
* s2_vpath
)
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
++)
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:
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
140 pathcmp (const vfs_path_t
* p1_vpath
, const vfs_path_t
* p2_vpath
)
146 p1
= fp1
= vfs_path_to_str (p1_vpath
);
147 p2
= fp2
= vfs_path_to_str (p2_vpath
);
149 for (; *p1
== *p2
; p1
++, p2
++)
159 else if (*p2
== '\0')
161 else if (*p1
== PATH_SEP
)
163 else if (*p2
== PATH_SEP
)
166 ret_val
= (*p1
- *p2
);
173 /* --------------------------------------------------------------------------------------------- */
176 decode (char *buffer
)
178 char *res
= g_strdup (buffer
);
181 for (p
= q
= res
; *p
; p
++, q
++)
212 /* --------------------------------------------------------------------------------------------- */
213 /** Loads the tree store from the specified filename */
216 tree_store_load_from (char *name
)
219 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
224 g_return_val_if_fail (name
!= NULL
, FALSE
);
229 file
= fopen (name
, "r");
233 if (fgets (buffer
, sizeof (buffer
), file
) != NULL
)
235 if (strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0)
253 /* File open -> read contents */
255 while (fgets (buffer
, MC_MAXPATHLEN
, file
))
261 /* Skip invalid records */
262 if ((buffer
[0] != '0' && buffer
[0] != '1'))
265 if (buffer
[1] != ':')
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
, " ");
279 different
= strtok (NULL
, "");
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
);
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
);
317 /* Nothing loaded, we add some standard directories */
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
);
330 /* --------------------------------------------------------------------------------------------- */
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
);
343 /* --------------------------------------------------------------------------------------------- */
344 /** Saves the tree to the specified filename */
347 tree_store_save_to (char *name
)
352 file
= fopen (name
, "w");
356 fprintf (file
, "%s\n", TREE_SIGNATURE
);
358 current
= ts
.tree_first
;
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
);
375 char *encoded
= encode (current
->name
, 0);
377 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
383 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"),
384 name
, unix_error_string (errno
));
388 current
= current
->next
;
390 tree_store_dirty (FALSE
);
396 /* --------------------------------------------------------------------------------------------- */
399 tree_store_add_entry (const vfs_path_t
* name
)
402 tree_entry
*current
= ts
.tree_first
;
403 tree_entry
*old
= NULL
;
407 if (ts
.tree_last
&& ts
.tree_last
->next
)
410 /* Search for the correct place */
411 while (current
!= NULL
&& (flag
= pathcmp (current
->name
, name
)) < 0)
414 current
= current
->next
;
418 return current
; /* Already in the list */
420 /* Not in the list -> add it */
421 new = g_new0 (tree_entry
, 1);
424 /* Append to the end of the list */
442 /* Insert in to the middle of the list */
446 /* Yes, in the middle */
447 new->next
= old
->next
;
452 /* Nope, in the beginning of the list */
453 new->next
= ts
.tree_first
;
456 new->next
->prev
= new;
459 /* Calculate attributes */
460 new->name
= vfs_path_clone (name
);
461 new->sublevel
= vfs_path_tokens_count (new->name
);
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
;
473 submask
= new->next
->submask
;
476 submask
|= 1 << new->sublevel
;
477 submask
&= (2 << new->sublevel
) - 1;
478 new->submask
= submask
;
481 /* Correct the submasks of the previous entries */
483 while (current
&& current
->sublevel
> new->sublevel
)
485 current
->submask
|= 1 << new->sublevel
;
486 current
= current
->prev
;
489 tree_store_dirty (TRUE
);
493 /* --------------------------------------------------------------------------------------------- */
496 tree_store_notify_remove (tree_entry
* entry
)
498 hook_t
*p
= remove_entry_hooks
;
499 tree_store_remove_fn r
;
503 r
= (tree_store_remove_fn
) p
->hook_fn
;
504 r (entry
, p
->hook_data
);
509 /* --------------------------------------------------------------------------------------------- */
512 remove_entry (tree_entry
* entry
)
514 tree_entry
*current
= entry
->prev
;
516 tree_entry
*ret
= NULL
;
518 tree_store_notify_remove (entry
);
520 /* Correct the submasks of the previous entries */
522 submask
= entry
->next
->submask
;
523 while (current
&& current
->sublevel
> entry
->sublevel
)
525 submask
|= 1 << current
->sublevel
;
526 submask
&= (2 << current
->sublevel
) - 1;
527 current
->submask
= submask
;
528 current
= current
->prev
;
531 /* Unlink the entry from the list */
533 entry
->prev
->next
= entry
->next
;
535 ts
.tree_first
= entry
->next
;
538 entry
->next
->prev
= entry
->prev
;
540 ts
.tree_last
= entry
->prev
;
542 /* Free the memory used by the entry */
543 g_free (entry
->name
);
549 /* --------------------------------------------------------------------------------------------- */
552 process_special_dirs (GList
** special_dirs
, char *file
)
554 gchar
**buffers
, **start_buff
;
558 cfg
= mc_config_init (file
, TRUE
);
562 start_buff
= buffers
= mc_config_get_string_list (cfg
, "Special dirs", "list", &buffers_len
);
565 while (*buffers
!= NULL
)
567 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
571 g_strfreev (start_buff
);
573 mc_config_deinit (cfg
);
576 /* --------------------------------------------------------------------------------------------- */
579 should_skip_directory (const vfs_path_t
* vpath
)
581 static GList
*special_dirs
= NULL
;
583 static gboolean loaded
= FALSE
;
585 gboolean ret
= FALSE
;
591 process_special_dirs (&special_dirs
, profile_name
);
592 process_special_dirs (&special_dirs
, global_profile_name
);
595 dir
= vfs_path_to_str (vpath
);
596 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
597 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
607 /* --------------------------------------------------------------------------------------------- */
608 /*** public functions ****************************************************************************/
609 /* --------------------------------------------------------------------------------------------- */
611 /* Searches for specified directory */
613 tree_store_whereis (const vfs_path_t
* name
)
615 tree_entry
*current
= ts
.tree_first
;
618 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
619 current
= current
->next
;
627 /* --------------------------------------------------------------------------------------------- */
630 tree_store_get (void)
635 /* --------------------------------------------------------------------------------------------- */
637 * \fn int tree_store_load(void)
638 * \brief Loads the tree from the default location
639 * \return 1 if success (true), 0 otherwise (false)
643 tree_store_load (void)
648 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
649 retval
= tree_store_load_from (name
);
655 /* --------------------------------------------------------------------------------------------- */
657 * \fn int tree_store_save(void)
658 * \brief Saves the tree to the default file in an atomic fashion
659 * \return 0 if success, errno on error
663 tree_store_save (void)
668 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
669 mc_util_make_backup_if_possible (name
, ".tmp");
671 retval
= tree_store_save_to (name
);
674 mc_util_restore_from_backup_if_possible (name
, ".tmp");
679 mc_util_unlink_backup_if_possible (name
, ".tmp");
684 /* --------------------------------------------------------------------------------------------- */
687 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
689 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
692 /* --------------------------------------------------------------------------------------------- */
695 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
697 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
701 /* --------------------------------------------------------------------------------------------- */
704 tree_store_remove_entry (const vfs_path_t
* name_vpath
)
706 tree_entry
*current
, *base
, *old
;
709 g_return_if_fail (name_vpath
!= NULL
);
711 /* Miguel Ugly hack */
716 name
= vfs_path_to_str (name_vpath
);
717 is_root
= (name
[0] == PATH_SEP
&& name
[1] == '\0');
722 /* Miguel Ugly hack end */
724 base
= tree_store_whereis (name_vpath
);
726 return; /* Doesn't exist */
728 len
= vfs_path_len (base
->name
);
729 current
= base
->next
;
730 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, base
->name
, len
) == 0)
735 current_name
= vfs_path_to_str (current
->name
);
736 ok
= (current_name
[len
] == '\0' || current_name
[len
] == PATH_SEP
);
737 g_free (current_name
);
741 current
= current
->next
;
745 tree_store_dirty (TRUE
);
748 /* --------------------------------------------------------------------------------------------- */
749 /** This subdirectory exists -> clear deletion mark */
752 tree_store_mark_checked (const char *subname
)
756 tree_entry
*current
, *base
;
761 if (ts
.check_name
== NULL
)
764 /* Calculate the full name of the subdirectory */
765 if (subname
[0] == '.' && (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
768 check_name
= vfs_path_to_str (ts
.check_name
);
769 if (check_name
[0] == PATH_SEP
&& check_name
[1] == 0)
770 name
= vfs_path_build_filename (PATH_SEP_STR
, subname
, NULL
);
772 name
= vfs_path_append_new (ts
.check_name
, subname
, NULL
);
775 /* Search for the subdirectory */
776 current
= ts
.check_start
;
777 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
778 current
= current
->next
;
782 /* Doesn't exist -> add it */
783 current
= tree_store_add_entry (name
);
784 ts
.add_queue_vpath
= g_list_prepend (ts
.add_queue_vpath
, vfs_path_clone (name
));
788 /* Clear the deletion mark from the subdirectory and its children */
794 len
= vfs_path_len (base
->name
);
796 current
= base
->next
;
797 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, base
->name
, len
) == 0)
801 check_name
= vfs_path_to_str (current
->name
);
802 ok
= (check_name
[len
] == '\0' || check_name
[len
] == PATH_SEP
|| len
== 1);
807 current
= current
->next
;
812 /* --------------------------------------------------------------------------------------------- */
813 /** Mark the subdirectories of the current directory for delete */
816 tree_store_start_check (const vfs_path_t
* vpath
)
818 tree_entry
*current
, *retval
;
824 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
825 ts
.check_start
= NULL
;
827 /* Search for the start of subdirectories */
828 current
= tree_store_whereis (vpath
);
833 if (mc_stat (vpath
, &s
) == -1)
836 if (!S_ISDIR (s
.st_mode
))
839 current
= tree_store_add_entry (vpath
);
840 ts
.check_name
= vfs_path_clone (vpath
);
845 ts
.check_name
= vfs_path_clone (vpath
);
849 /* Mark old subdirectories for delete */
850 ts
.check_start
= current
->next
;
851 len
= vfs_path_len (ts
.check_name
);
853 current
= ts
.check_start
;
854 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, ts
.check_name
, len
) == 0)
859 current_name
= vfs_path_to_str (current
->name
);
860 ok
= (current_name
[len
] == '\0' || current_name
[len
] == PATH_SEP
|| len
== 1);
861 g_free (current_name
);
865 current
= current
->next
;
871 /* --------------------------------------------------------------------------------------------- */
872 /** Delete subdirectories which still have the deletion mark */
875 tree_store_end_check (void)
877 tree_entry
*current
, *old
;
884 g_return_if_fail (ts
.check_name
!= NULL
);
886 /* Check delete marks and delete if found */
887 len
= vfs_path_len (ts
.check_name
);
889 current
= ts
.check_start
;
890 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, ts
.check_name
, len
) == 0)
895 current_name
= vfs_path_to_str (current
->name
);
896 ok
= (current_name
[len
] == '\0' || current_name
[len
] == PATH_SEP
|| len
== 1);
897 g_free (current_name
);
902 current
= current
->next
;
907 /* get the stuff in the scan order */
908 ts
.add_queue_vpath
= g_list_reverse (ts
.add_queue_vpath
);
909 the_queue
= ts
.add_queue_vpath
;
910 ts
.add_queue_vpath
= NULL
;
911 vfs_path_free (ts
.check_name
);
912 ts
.check_name
= NULL
;
914 g_list_foreach (the_queue
, (GFunc
) vfs_path_free
, NULL
);
915 g_list_free (the_queue
);
918 /* --------------------------------------------------------------------------------------------- */
921 tree_store_rescan (const vfs_path_t
* vpath
)
928 if (should_skip_directory (vpath
))
930 entry
= tree_store_add_entry (vpath
);
935 entry
= tree_store_start_check (vpath
);
939 dirp
= mc_opendir (vpath
);
942 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
944 vfs_path_t
*tmp_vpath
;
946 if (dp
->d_name
[0] == '.')
948 if (dp
->d_name
[1] == 0 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
952 tmp_vpath
= vfs_path_append_new (vpath
, dp
->d_name
, NULL
);
953 if (mc_lstat (tmp_vpath
, &buf
) != -1)
955 if (S_ISDIR (buf
.st_mode
))
956 tree_store_mark_checked (dp
->d_name
);
958 vfs_path_free (tmp_vpath
);
962 tree_store_end_check ();
968 /* --------------------------------------------------------------------------------------------- */