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
&& (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 */
441 /* Insert in to the middle of the list */
445 /* Yes, in the middle */
446 new->next
= old
->next
;
451 /* Nope, in the beginning of the list */
452 new->next
= ts
.tree_first
;
455 new->next
->prev
= new;
458 /* Calculate attributes */
459 new->name
= vfs_path_clone (name
);
460 new->sublevel
= vfs_path_tokens_count (new->name
) + 1;
462 const char *new_name
;
464 new_name
= vfs_path_get_last_path_str (new->name
);
465 new->subname
= strrchr (new_name
, '/');
466 if (new->subname
== NULL
)
467 new->subname
= new_name
;
470 submask
= new->next
->submask
;
473 submask
|= 1 << new->sublevel
;
474 submask
&= (2 << new->sublevel
) - 1;
475 new->submask
= submask
;
478 /* Correct the submasks of the previous entries */
480 while (current
&& current
->sublevel
> new->sublevel
)
482 current
->submask
|= 1 << new->sublevel
;
483 current
= current
->prev
;
486 /* The entry has now been added */
488 if (new->sublevel
> 1)
490 /* Let's check if the parent directory is in the tree */
491 vfs_path_t
*tmp_vpath
;
493 tmp_vpath
= vfs_path_vtokens_get (new->name
, 0, new->sublevel
- 1);
494 tree_store_add_entry (tmp_vpath
);
495 vfs_path_free (tmp_vpath
);
498 tree_store_dirty (TRUE
);
502 /* --------------------------------------------------------------------------------------------- */
505 tree_store_notify_remove (tree_entry
* entry
)
507 hook_t
*p
= remove_entry_hooks
;
508 tree_store_remove_fn r
;
512 r
= (tree_store_remove_fn
) p
->hook_fn
;
513 r (entry
, p
->hook_data
);
518 /* --------------------------------------------------------------------------------------------- */
521 remove_entry (tree_entry
* entry
)
523 tree_entry
*current
= entry
->prev
;
525 tree_entry
*ret
= NULL
;
527 tree_store_notify_remove (entry
);
529 /* Correct the submasks of the previous entries */
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 */
542 entry
->prev
->next
= entry
->next
;
544 ts
.tree_first
= entry
->next
;
547 entry
->next
->prev
= entry
->prev
;
549 ts
.tree_last
= entry
->prev
;
551 /* Free the memory used by the entry */
552 g_free (entry
->name
);
558 /* --------------------------------------------------------------------------------------------- */
561 process_special_dirs (GList
** special_dirs
, char *file
)
563 gchar
**buffers
, **start_buff
;
567 cfg
= mc_config_init (file
);
571 start_buff
= buffers
= mc_config_get_string_list (cfg
, "Special dirs", "list", &buffers_len
);
574 while (*buffers
!= NULL
)
576 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
580 g_strfreev (start_buff
);
582 mc_config_deinit (cfg
);
585 /* --------------------------------------------------------------------------------------------- */
588 should_skip_directory (const vfs_path_t
* vpath
)
590 static GList
*special_dirs
= NULL
;
592 static gboolean loaded
= FALSE
;
594 gboolean ret
= FALSE
;
600 process_special_dirs (&special_dirs
, profile_name
);
601 process_special_dirs (&special_dirs
, global_profile_name
);
604 dir
= vfs_path_to_str (vpath
);
605 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
606 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
616 /* --------------------------------------------------------------------------------------------- */
617 /*** public functions ****************************************************************************/
618 /* --------------------------------------------------------------------------------------------- */
620 /* Searches for specified directory */
622 tree_store_whereis (const vfs_path_t
* name
)
624 tree_entry
*current
= ts
.tree_first
;
627 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
628 current
= current
->next
;
636 /* --------------------------------------------------------------------------------------------- */
639 tree_store_get (void)
644 /* --------------------------------------------------------------------------------------------- */
646 * \fn int tree_store_load(void)
647 * \brief Loads the tree from the default location
648 * \return 1 if success (true), 0 otherwise (false)
652 tree_store_load (void)
657 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
658 retval
= tree_store_load_from (name
);
664 /* --------------------------------------------------------------------------------------------- */
666 * \fn int tree_store_save(void)
667 * \brief Saves the tree to the default file in an atomic fashion
668 * \return 0 if success, errno on error
672 tree_store_save (void)
677 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
678 mc_util_make_backup_if_possible (name
, ".tmp");
680 retval
= tree_store_save_to (name
);
683 mc_util_restore_from_backup_if_possible (name
, ".tmp");
688 mc_util_unlink_backup_if_possible (name
, ".tmp");
693 /* --------------------------------------------------------------------------------------------- */
696 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
698 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
701 /* --------------------------------------------------------------------------------------------- */
704 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
706 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
710 /* --------------------------------------------------------------------------------------------- */
713 tree_store_remove_entry (const vfs_path_t
* name_vpath
)
715 tree_entry
*current
, *base
, *old
;
718 g_return_if_fail (name_vpath
!= NULL
);
720 /* Miguel Ugly hack */
725 name
= vfs_path_to_str (name_vpath
);
726 is_root
= (name
[0] == PATH_SEP
&& name
[1] == '\0');
731 /* Miguel Ugly hack end */
733 base
= tree_store_whereis (name_vpath
);
735 return; /* Doesn't exist */
737 len
= vfs_path_len (base
->name
);
738 current
= base
->next
;
739 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, base
->name
, len
) == 0)
744 current_name
= vfs_path_to_str (current
->name
);
745 ok
= (current_name
[len
] == '\0' || current_name
[len
] == PATH_SEP
);
746 g_free (current_name
);
750 current
= current
->next
;
754 tree_store_dirty (TRUE
);
757 /* --------------------------------------------------------------------------------------------- */
758 /** This subdirectory exists -> clear deletion mark */
761 tree_store_mark_checked (const char *subname
)
765 tree_entry
*current
, *base
;
770 if (ts
.check_name
== NULL
)
773 /* Calculate the full name of the subdirectory */
774 if (subname
[0] == '.' && (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
777 check_name
= vfs_path_to_str (ts
.check_name
);
778 if (check_name
[0] == PATH_SEP
&& check_name
[1] == 0)
779 name
= vfs_path_build_filename (PATH_SEP_STR
, subname
, NULL
);
781 name
= vfs_path_append_new (ts
.check_name
, subname
, NULL
);
784 /* Search for the subdirectory */
785 current
= ts
.check_start
;
786 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
787 current
= current
->next
;
791 /* Doesn't exist -> add it */
792 current
= tree_store_add_entry (name
);
793 ts
.add_queue_vpath
= g_list_prepend (ts
.add_queue_vpath
, vfs_path_clone (name
));
797 /* Clear the deletion mark from the subdirectory and its children */
803 len
= vfs_path_len (base
->name
);
805 current
= base
->next
;
806 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, base
->name
, len
) == 0)
810 check_name
= vfs_path_to_str (current
->name
);
811 ok
= (check_name
[len
] == '\0' || check_name
[len
] == PATH_SEP
|| len
== 1);
816 current
= current
->next
;
821 /* --------------------------------------------------------------------------------------------- */
822 /** Mark the subdirectories of the current directory for delete */
825 tree_store_start_check (const vfs_path_t
* vpath
)
827 tree_entry
*current
, *retval
;
833 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
834 ts
.check_start
= NULL
;
836 /* Search for the start of subdirectories */
837 current
= tree_store_whereis (vpath
);
842 if (mc_stat (vpath
, &s
) == -1)
845 if (!S_ISDIR (s
.st_mode
))
848 current
= tree_store_add_entry (vpath
);
849 ts
.check_name
= vfs_path_clone (vpath
);
854 ts
.check_name
= vfs_path_clone (vpath
);
858 /* Mark old subdirectories for delete */
859 ts
.check_start
= current
->next
;
860 len
= vfs_path_len (ts
.check_name
);
862 current
= ts
.check_start
;
863 while (current
!= NULL
&& vfs_path_cmp (current
->name
, ts
.check_name
) == 0)
868 current_name
= vfs_path_to_str (current
->name
);
869 ok
= (current_name
[len
] == '\0' || (current_name
[len
] == PATH_SEP
|| len
== 1));
870 g_free (current_name
);
874 current
= current
->next
;
880 /* --------------------------------------------------------------------------------------------- */
881 /** Delete subdirectories which still have the deletion mark */
884 tree_store_end_check (void)
886 tree_entry
*current
, *old
;
893 g_return_if_fail (ts
.check_name
!= NULL
);
895 /* Check delete marks and delete if found */
896 len
= vfs_path_len (ts
.check_name
);
898 current
= ts
.check_start
;
899 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, ts
.check_name
, len
) == 0)
904 current_name
= vfs_path_to_str (current
->name
);
905 ok
= (current_name
[len
] == '\0' || current_name
[len
] == PATH_SEP
|| len
== 1);
906 g_free (current_name
);
911 current
= current
->next
;
916 /* get the stuff in the scan order */
917 ts
.add_queue_vpath
= g_list_reverse (ts
.add_queue_vpath
);
918 the_queue
= ts
.add_queue_vpath
;
919 ts
.add_queue_vpath
= NULL
;
920 g_free (ts
.check_name
);
921 ts
.check_name
= NULL
;
923 g_list_foreach (the_queue
, (GFunc
) vfs_path_free
, NULL
);
924 g_list_free (the_queue
);
927 /* --------------------------------------------------------------------------------------------- */
930 tree_store_rescan (const vfs_path_t
* vpath
)
937 if (should_skip_directory (vpath
))
939 entry
= tree_store_add_entry (vpath
);
944 entry
= tree_store_start_check (vpath
);
948 dirp
= mc_opendir (vpath
);
951 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
953 vfs_path_t
*tmp_vpath
;
955 if (dp
->d_name
[0] == '.')
957 if (dp
->d_name
[1] == 0 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
961 tmp_vpath
= vfs_path_append_new (vpath
, dp
->d_name
, NULL
);
962 if (mc_lstat (tmp_vpath
, &buf
) != -1)
964 if (S_ISDIR (buf
.st_mode
))
965 tree_store_mark_checked (dp
->d_name
);
967 vfs_path_free (tmp_vpath
);
971 tree_store_end_check ();
977 /* --------------------------------------------------------------------------------------------- */