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
) + 1;
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
;
471 submask
= new->next
->submask
;
474 submask
|= 1 << new->sublevel
;
475 submask
&= (2 << new->sublevel
) - 1;
476 new->submask
= submask
;
479 /* Correct the submasks of the previous entries */
481 while (current
&& current
->sublevel
> new->sublevel
)
483 current
->submask
|= 1 << new->sublevel
;
484 current
= current
->prev
;
487 /* The entry has now been added */
489 if (new->sublevel
> 1)
491 /* Let's check if the parent directory is in the tree */
492 vfs_path_t
*tmp_vpath
;
494 tmp_vpath
= vfs_path_vtokens_get (new->name
, 0, new->sublevel
- 1);
495 tree_store_add_entry (tmp_vpath
);
496 vfs_path_free (tmp_vpath
);
499 tree_store_dirty (TRUE
);
503 /* --------------------------------------------------------------------------------------------- */
506 tree_store_notify_remove (tree_entry
* entry
)
508 hook_t
*p
= remove_entry_hooks
;
509 tree_store_remove_fn r
;
513 r
= (tree_store_remove_fn
) p
->hook_fn
;
514 r (entry
, p
->hook_data
);
519 /* --------------------------------------------------------------------------------------------- */
522 remove_entry (tree_entry
* entry
)
524 tree_entry
*current
= entry
->prev
;
526 tree_entry
*ret
= NULL
;
528 tree_store_notify_remove (entry
);
530 /* Correct the submasks of the previous entries */
532 submask
= entry
->next
->submask
;
533 while (current
&& current
->sublevel
> entry
->sublevel
)
535 submask
|= 1 << current
->sublevel
;
536 submask
&= (2 << current
->sublevel
) - 1;
537 current
->submask
= submask
;
538 current
= current
->prev
;
541 /* Unlink the entry from the list */
543 entry
->prev
->next
= entry
->next
;
545 ts
.tree_first
= entry
->next
;
548 entry
->next
->prev
= entry
->prev
;
550 ts
.tree_last
= entry
->prev
;
552 /* Free the memory used by the entry */
553 g_free (entry
->name
);
559 /* --------------------------------------------------------------------------------------------- */
562 process_special_dirs (GList
** special_dirs
, char *file
)
564 gchar
**buffers
, **start_buff
;
568 cfg
= mc_config_init (file
);
572 start_buff
= buffers
= mc_config_get_string_list (cfg
, "Special dirs", "list", &buffers_len
);
575 while (*buffers
!= NULL
)
577 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
581 g_strfreev (start_buff
);
583 mc_config_deinit (cfg
);
586 /* --------------------------------------------------------------------------------------------- */
589 should_skip_directory (const vfs_path_t
* vpath
)
591 static GList
*special_dirs
= NULL
;
593 static gboolean loaded
= FALSE
;
595 gboolean ret
= FALSE
;
601 process_special_dirs (&special_dirs
, profile_name
);
602 process_special_dirs (&special_dirs
, global_profile_name
);
605 dir
= vfs_path_to_str (vpath
);
606 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
607 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
617 /* --------------------------------------------------------------------------------------------- */
618 /*** public functions ****************************************************************************/
619 /* --------------------------------------------------------------------------------------------- */
621 /* Searches for specified directory */
623 tree_store_whereis (const vfs_path_t
* name
)
625 tree_entry
*current
= ts
.tree_first
;
628 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
629 current
= current
->next
;
637 /* --------------------------------------------------------------------------------------------- */
640 tree_store_get (void)
645 /* --------------------------------------------------------------------------------------------- */
647 * \fn int tree_store_load(void)
648 * \brief Loads the tree from the default location
649 * \return 1 if success (true), 0 otherwise (false)
653 tree_store_load (void)
658 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
659 retval
= tree_store_load_from (name
);
665 /* --------------------------------------------------------------------------------------------- */
667 * \fn int tree_store_save(void)
668 * \brief Saves the tree to the default file in an atomic fashion
669 * \return 0 if success, errno on error
673 tree_store_save (void)
678 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
679 mc_util_make_backup_if_possible (name
, ".tmp");
681 retval
= tree_store_save_to (name
);
684 mc_util_restore_from_backup_if_possible (name
, ".tmp");
689 mc_util_unlink_backup_if_possible (name
, ".tmp");
694 /* --------------------------------------------------------------------------------------------- */
697 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
699 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
702 /* --------------------------------------------------------------------------------------------- */
705 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
707 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
711 /* --------------------------------------------------------------------------------------------- */
714 tree_store_remove_entry (const vfs_path_t
* name_vpath
)
716 tree_entry
*current
, *base
, *old
;
719 g_return_if_fail (name_vpath
!= NULL
);
721 /* Miguel Ugly hack */
726 name
= vfs_path_to_str (name_vpath
);
727 is_root
= (name
[0] == PATH_SEP
&& name
[1] == '\0');
732 /* Miguel Ugly hack end */
734 base
= tree_store_whereis (name_vpath
);
736 return; /* Doesn't exist */
738 len
= vfs_path_len (base
->name
);
739 current
= base
->next
;
740 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, base
->name
, len
) == 0)
745 current_name
= vfs_path_to_str (current
->name
);
746 ok
= (current_name
[len
] == '\0' || current_name
[len
] == PATH_SEP
);
747 g_free (current_name
);
751 current
= current
->next
;
755 tree_store_dirty (TRUE
);
758 /* --------------------------------------------------------------------------------------------- */
759 /** This subdirectory exists -> clear deletion mark */
762 tree_store_mark_checked (const char *subname
)
766 tree_entry
*current
, *base
;
771 if (ts
.check_name
== NULL
)
774 /* Calculate the full name of the subdirectory */
775 if (subname
[0] == '.' && (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
778 check_name
= vfs_path_to_str (ts
.check_name
);
779 if (check_name
[0] == PATH_SEP
&& check_name
[1] == 0)
780 name
= vfs_path_build_filename (PATH_SEP_STR
, subname
, NULL
);
782 name
= vfs_path_append_new (ts
.check_name
, subname
, NULL
);
785 /* Search for the subdirectory */
786 current
= ts
.check_start
;
787 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
788 current
= current
->next
;
792 /* Doesn't exist -> add it */
793 current
= tree_store_add_entry (name
);
794 ts
.add_queue_vpath
= g_list_prepend (ts
.add_queue_vpath
, vfs_path_clone (name
));
798 /* Clear the deletion mark from the subdirectory and its children */
804 len
= vfs_path_len (base
->name
);
806 current
= base
->next
;
807 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, base
->name
, len
) == 0)
811 check_name
= vfs_path_to_str (current
->name
);
812 ok
= (check_name
[len
] == '\0' || check_name
[len
] == PATH_SEP
|| len
== 1);
817 current
= current
->next
;
822 /* --------------------------------------------------------------------------------------------- */
823 /** Mark the subdirectories of the current directory for delete */
826 tree_store_start_check (const vfs_path_t
* vpath
)
828 tree_entry
*current
, *retval
;
834 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
835 ts
.check_start
= NULL
;
837 /* Search for the start of subdirectories */
838 current
= tree_store_whereis (vpath
);
843 if (mc_stat (vpath
, &s
) == -1)
846 if (!S_ISDIR (s
.st_mode
))
849 current
= tree_store_add_entry (vpath
);
850 ts
.check_name
= vfs_path_clone (vpath
);
855 ts
.check_name
= vfs_path_clone (vpath
);
859 /* Mark old subdirectories for delete */
860 ts
.check_start
= current
->next
;
861 len
= vfs_path_len (ts
.check_name
);
863 current
= ts
.check_start
;
864 while (current
!= NULL
&& vfs_path_cmp (current
->name
, ts
.check_name
) == 0)
869 current_name
= vfs_path_to_str (current
->name
);
870 ok
= (current_name
[len
] == '\0' || (current_name
[len
] == PATH_SEP
|| len
== 1));
871 g_free (current_name
);
875 current
= current
->next
;
881 /* --------------------------------------------------------------------------------------------- */
882 /** Delete subdirectories which still have the deletion mark */
885 tree_store_end_check (void)
887 tree_entry
*current
, *old
;
894 g_return_if_fail (ts
.check_name
!= NULL
);
896 /* Check delete marks and delete if found */
897 len
= vfs_path_len (ts
.check_name
);
899 current
= ts
.check_start
;
900 while (current
!= NULL
&& vfs_path_ncmp (current
->name
, ts
.check_name
, len
) == 0)
905 current_name
= vfs_path_to_str (current
->name
);
906 ok
= (current_name
[len
] == '\0' || current_name
[len
] == PATH_SEP
|| len
== 1);
907 g_free (current_name
);
912 current
= current
->next
;
917 /* get the stuff in the scan order */
918 ts
.add_queue_vpath
= g_list_reverse (ts
.add_queue_vpath
);
919 the_queue
= ts
.add_queue_vpath
;
920 ts
.add_queue_vpath
= NULL
;
921 g_free (ts
.check_name
);
922 ts
.check_name
= NULL
;
924 g_list_foreach (the_queue
, (GFunc
) vfs_path_free
, NULL
);
925 g_list_free (the_queue
);
928 /* --------------------------------------------------------------------------------------------- */
931 tree_store_rescan (const vfs_path_t
* vpath
)
938 if (should_skip_directory (vpath
))
940 entry
= tree_store_add_entry (vpath
);
945 entry
= tree_store_start_check (vpath
);
949 dirp
= mc_opendir (vpath
);
952 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
954 vfs_path_t
*tmp_vpath
;
956 if (dp
->d_name
[0] == '.')
958 if (dp
->d_name
[1] == 0 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
962 tmp_vpath
= vfs_path_append_new (vpath
, dp
->d_name
, NULL
);
963 if (mc_lstat (tmp_vpath
, &buf
) != -1)
965 if (S_ISDIR (buf
.st_mode
))
966 tree_store_mark_checked (dp
->d_name
);
968 vfs_path_free (tmp_vpath
);
972 tree_store_end_check ();
978 /* --------------------------------------------------------------------------------------------- */