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"
59 #include "src/setup.h"
61 #include "treestore.h"
63 /*** global variables ****************************************************************************/
65 /*** file scope macro definitions ****************************************************************/
67 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
69 /*** file scope type declarations ****************************************************************/
71 /*** file scope variables ************************************************************************/
73 static struct TreeStore ts
;
75 static hook_t
*remove_entry_hooks
;
77 /*** file scope functions ************************************************************************/
78 /* --------------------------------------------------------------------------------------------- */
80 static tree_entry
*tree_store_add_entry (const char *name
);
82 /* --------------------------------------------------------------------------------------------- */
85 tree_store_dirty (int state
)
90 /* --------------------------------------------------------------------------------------------- */
91 /** Returns the number of common bytes in the strings. */
94 str_common (const char *s1
, const char *s2
)
98 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
103 /* --------------------------------------------------------------------------------------------- */
104 /* The directory names are arranged in a single linked list in the same
105 order as they are displayed. When the tree is displayed the expected
116 i.e. the required collating sequence when comparing two directory names is
117 '\0' < PATH_SEP < all-other-characters-in-encoding-order
119 Since strcmp doesn't fulfil this requirement we use pathcmp when
120 inserting directory names into the list. The meaning of the return value
121 of pathcmp and strcmp are the same (an integer less than, equal to, or
122 greater than zero if p1 is found to be less than, to match, or be greater
127 pathcmp (const char *p1
, const char *p2
)
129 for (; *p1
== *p2
; p1
++, p2
++)
144 /* --------------------------------------------------------------------------------------------- */
147 decode (char *buffer
)
149 char *res
= g_strdup (buffer
);
152 for (p
= q
= res
; *p
; p
++, q
++)
183 /* --------------------------------------------------------------------------------------------- */
184 /** Loads the tree store from the specified filename */
187 tree_store_load_from (char *name
)
190 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
195 g_return_val_if_fail (name
!= NULL
, FALSE
);
200 file
= fopen (name
, "r");
204 if (fgets (buffer
, sizeof (buffer
), file
) != NULL
)
206 if (strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0)
224 /* File open -> read contents */
226 while (fgets (buffer
, MC_MAXPATHLEN
, file
))
232 /* Skip invalid records */
233 if ((buffer
[0] != '0' && buffer
[0] != '1'))
236 if (buffer
[1] != ':')
239 scanned
= buffer
[0] == '1';
241 lc_name
= decode (buffer
+ 2);
242 if (lc_name
[0] != PATH_SEP
)
244 /* Clear-text decompression */
245 char *s
= strtok (lc_name
, " ");
250 different
= strtok (NULL
, "");
253 vfs_path_t
*vpath
= vfs_path_from_str (oldname
);
254 strcpy (oldname
+ common
, different
);
255 if (vfs_file_is_local (vpath
))
257 e
= tree_store_add_entry (oldname
);
258 e
->scanned
= scanned
;
260 vfs_path_free (vpath
);
266 vfs_path_t
*vpath
= vfs_path_from_str (lc_name
);
267 if (vfs_file_is_local (vpath
))
269 e
= tree_store_add_entry (lc_name
);
270 e
->scanned
= scanned
;
272 vfs_path_free (vpath
);
273 strcpy (oldname
, lc_name
);
280 /* Nothing loaded, we add some standard directories */
283 vfs_path_t
*tmp_vpath
= vfs_path_from_str (PATH_SEP_STR
);
284 tree_store_add_entry (PATH_SEP_STR
);
285 tree_store_rescan (tmp_vpath
);
286 vfs_path_free (tmp_vpath
);
293 /* --------------------------------------------------------------------------------------------- */
296 encode (const char *string
)
303 for (special_chars
= 0, p
= string
; *p
; p
++)
305 if (*p
== '\n' || *p
== '\\')
309 res
= g_malloc (p
- string
+ special_chars
+ 1);
310 for (p
= string
, q
= res
; *p
; p
++, q
++)
312 if (*p
!= '\n' && *p
!= '\\')
335 /* --------------------------------------------------------------------------------------------- */
336 /** Saves the tree to the specified filename */
339 tree_store_save_to (char *name
)
344 file
= fopen (name
, "w");
348 fprintf (file
, "%s\n", TREE_SIGNATURE
);
350 current
= ts
.tree_first
;
354 vfs_path_t
*vpath
= vfs_path_from_str (current
->name
);
356 if (vfs_file_is_local (vpath
))
358 /* Clear-text compression */
359 if (current
->prev
&& (common
= str_common (current
->prev
->name
, current
->name
)) > 2)
361 char *encoded
= encode (current
->name
+ common
);
363 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
, common
, encoded
);
368 char *encoded
= encode (current
->name
);
370 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
376 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"),
377 name
, unix_error_string (errno
));
378 vfs_path_free (vpath
);
382 vfs_path_free (vpath
);
383 current
= current
->next
;
385 tree_store_dirty (FALSE
);
391 /* --------------------------------------------------------------------------------------------- */
394 tree_store_add_entry (const char *name
)
397 tree_entry
*current
= ts
.tree_first
;
398 tree_entry
*old
= NULL
;
403 if (ts
.tree_last
&& ts
.tree_last
->next
)
406 /* Search for the correct place */
407 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
410 current
= current
->next
;
414 return current
; /* Already in the list */
416 /* Not in the list -> add it */
417 new = g_new0 (tree_entry
, 1);
420 /* Append to the end of the list */
437 /* Insert in to the middle of the list */
441 /* Yes, in the middle */
442 new->next
= old
->next
;
447 /* Nope, in the beginning of the list */
448 new->next
= ts
.tree_first
;
451 new->next
->prev
= new;
454 /* Calculate attributes */
455 new->name
= g_strdup (name
);
456 len
= strlen (new->name
);
458 for (i
= 0; i
< len
; i
++)
459 if (new->name
[i
] == PATH_SEP
)
462 new->subname
= new->name
+ i
+ 1;
465 submask
= new->next
->submask
;
468 submask
|= 1 << new->sublevel
;
469 submask
&= (2 << new->sublevel
) - 1;
470 new->submask
= submask
;
473 /* Correct the submasks of the previous entries */
475 while (current
&& current
->sublevel
> new->sublevel
)
477 current
->submask
|= 1 << new->sublevel
;
478 current
= current
->prev
;
481 /* The entry has now been added */
483 if (new->sublevel
> 1)
485 /* Let's check if the parent directory is in the tree */
486 char *parent
= g_strdup (new->name
);
488 for (i
= strlen (parent
) - 1; i
> 1; i
--)
490 if (parent
[i
] == PATH_SEP
)
493 tree_store_add_entry (parent
);
500 tree_store_dirty (TRUE
);
504 /* --------------------------------------------------------------------------------------------- */
507 tree_store_notify_remove (tree_entry
* entry
)
509 hook_t
*p
= remove_entry_hooks
;
510 tree_store_remove_fn r
;
514 r
= (tree_store_remove_fn
) p
->hook_fn
;
515 r (entry
, p
->hook_data
);
520 /* --------------------------------------------------------------------------------------------- */
523 remove_entry (tree_entry
* entry
)
525 tree_entry
*current
= entry
->prev
;
527 tree_entry
*ret
= NULL
;
529 tree_store_notify_remove (entry
);
531 /* Correct the submasks of the previous entries */
533 submask
= entry
->next
->submask
;
534 while (current
&& current
->sublevel
> entry
->sublevel
)
536 submask
|= 1 << current
->sublevel
;
537 submask
&= (2 << current
->sublevel
) - 1;
538 current
->submask
= submask
;
539 current
= current
->prev
;
542 /* Unlink the entry from the list */
544 entry
->prev
->next
= entry
->next
;
546 ts
.tree_first
= entry
->next
;
549 entry
->next
->prev
= entry
->prev
;
551 ts
.tree_last
= entry
->prev
;
553 /* Free the memory used by the entry */
554 g_free (entry
->name
);
560 /* --------------------------------------------------------------------------------------------- */
563 process_special_dirs (GList
** special_dirs
, char *file
)
565 gchar
**buffers
, **start_buff
;
569 cfg
= mc_config_init (file
);
573 start_buff
= buffers
= mc_config_get_string_list (cfg
, "Special dirs", "list", &buffers_len
);
576 while (*buffers
!= NULL
)
578 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
582 g_strfreev (start_buff
);
584 mc_config_deinit (cfg
);
587 /* --------------------------------------------------------------------------------------------- */
590 should_skip_directory (const char *dir
)
592 static GList
*special_dirs
= NULL
;
594 static gboolean loaded
= FALSE
;
600 process_special_dirs (&special_dirs
, profile_name
);
601 process_special_dirs (&special_dirs
, global_profile_name
);
604 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
605 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
611 /* --------------------------------------------------------------------------------------------- */
612 /*** public functions ****************************************************************************/
613 /* --------------------------------------------------------------------------------------------- */
615 /* Searches for specified directory */
617 tree_store_whereis (const char *name
)
619 tree_entry
*current
= ts
.tree_first
;
622 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
623 current
= current
->next
;
631 /* --------------------------------------------------------------------------------------------- */
634 tree_store_get (void)
639 /* --------------------------------------------------------------------------------------------- */
641 * \fn int tree_store_load(void)
642 * \brief Loads the tree from the default location
643 * \return 1 if success (true), 0 otherwise (false)
647 tree_store_load (void)
652 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
653 retval
= tree_store_load_from (name
);
659 /* --------------------------------------------------------------------------------------------- */
661 * \fn int tree_store_save(void)
662 * \brief Saves the tree to the default file in an atomic fashion
663 * \return 0 if success, errno on error
667 tree_store_save (void)
672 name
= mc_config_get_full_path (MC_TREESTORE_FILE
);
673 mc_util_make_backup_if_possible (name
, ".tmp");
675 retval
= tree_store_save_to (name
);
678 mc_util_restore_from_backup_if_possible (name
, ".tmp");
683 mc_util_unlink_backup_if_possible (name
, ".tmp");
688 /* --------------------------------------------------------------------------------------------- */
691 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
693 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
696 /* --------------------------------------------------------------------------------------------- */
699 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
701 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
705 /* --------------------------------------------------------------------------------------------- */
708 tree_store_remove_entry (const char *name
)
710 tree_entry
*current
, *base
, *old
;
713 g_return_if_fail (name
!= NULL
);
715 /* Miguel Ugly hack */
716 if (name
[0] == PATH_SEP
&& name
[1] == 0)
718 /* Miguel Ugly hack end */
720 base
= tree_store_whereis (name
);
722 return; /* Doesn't exist */
724 len
= strlen (base
->name
);
725 current
= base
->next
;
727 && strncmp (current
->name
, base
->name
, len
) == 0
728 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
))
731 current
= current
->next
;
735 tree_store_dirty (TRUE
);
740 /* --------------------------------------------------------------------------------------------- */
741 /** This subdirectory exists -> clear deletion mark */
744 tree_store_mark_checked (const char *subname
)
747 tree_entry
*current
, *base
;
752 if (ts
.check_name
== NULL
)
755 /* Calculate the full name of the subdirectory */
756 if (subname
[0] == '.' && (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
758 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
759 name
= g_strconcat (PATH_SEP_STR
, subname
, (char *) NULL
);
761 name
= concat_dir_and_file (ts
.check_name
, subname
);
763 /* Search for the subdirectory */
764 current
= ts
.check_start
;
765 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
766 current
= current
->next
;
770 /* Doesn't exist -> add it */
771 current
= tree_store_add_entry (name
);
772 ts
.add_queue
= g_list_prepend (ts
.add_queue
, g_strdup (name
));
776 /* Clear the deletion mark from the subdirectory and its children */
780 len
= strlen (base
->name
);
782 current
= base
->next
;
784 && strncmp (current
->name
, base
->name
, len
) == 0
785 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
788 current
= current
->next
;
793 /* --------------------------------------------------------------------------------------------- */
794 /** Mark the subdirectories of the current directory for delete */
797 tree_store_start_check (const char *path
)
799 tree_entry
*current
, *retval
;
805 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
806 ts
.check_start
= NULL
;
808 /* Search for the start of subdirectories */
809 current
= tree_store_whereis (path
);
813 vfs_path_t
*vpath
= vfs_path_from_str (path
);
815 if (mc_stat (vpath
, &s
) == -1)
817 vfs_path_free (vpath
);
820 vfs_path_free (vpath
);
822 if (!S_ISDIR (s
.st_mode
))
825 current
= tree_store_add_entry (path
);
826 ts
.check_name
= g_strdup (path
);
831 ts
.check_name
= g_strdup (path
);
835 /* Mark old subdirectories for delete */
836 ts
.check_start
= current
->next
;
837 len
= strlen (ts
.check_name
);
839 current
= ts
.check_start
;
841 && strncmp (current
->name
, ts
.check_name
, len
) == 0
842 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
845 current
= current
->next
;
851 /* --------------------------------------------------------------------------------------------- */
852 /** Delete subdirectories which still have the deletion mark */
855 tree_store_end_check (void)
857 tree_entry
*current
, *old
;
864 g_return_if_fail (ts
.check_name
!= NULL
);
866 /* Check delete marks and delete if found */
867 len
= strlen (ts
.check_name
);
869 current
= ts
.check_start
;
871 && strncmp (current
->name
, ts
.check_name
, len
) == 0
872 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
875 current
= current
->next
;
880 /* get the stuff in the scan order */
881 ts
.add_queue
= g_list_reverse (ts
.add_queue
);
882 the_queue
= ts
.add_queue
;
884 g_free (ts
.check_name
);
885 ts
.check_name
= NULL
;
887 g_list_foreach (the_queue
, (GFunc
) g_free
, NULL
);
888 g_list_free (the_queue
);
891 /* --------------------------------------------------------------------------------------------- */
894 tree_store_rescan (const vfs_path_t
* vpath
)
900 char *dir
= vfs_path_to_str (vpath
);
902 if (should_skip_directory (dir
))
904 entry
= tree_store_add_entry (dir
);
910 entry
= tree_store_start_check (dir
);
918 dirp
= mc_opendir (vpath
);
921 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
923 vfs_path_t
*tmp_vpath
;
925 if (dp
->d_name
[0] == '.')
927 if (dp
->d_name
[1] == 0 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
931 tmp_vpath
= vfs_path_append_new (vpath
, dp
->d_name
, NULL
);
932 if (mc_lstat (tmp_vpath
, &buf
) != -1)
934 if (S_ISDIR (buf
.st_mode
))
935 tree_store_mark_checked (dp
->d_name
);
937 vfs_path_free (tmp_vpath
);
941 tree_store_end_check ();
948 /* --------------------------------------------------------------------------------------------- */