4 * Contains a storage of the file system tree representation
6 Copyright (C) 1994, 1995, 1996, 1997 The Free Software Foundation
8 Written: 1994, 1996 Janne Kukonlehto
10 1996, 1999 Miguel de Icaza
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 This module has been converted to be a widget.
28 The program load and saves the tree each time the tree widget is
29 created and destroyed. This is required for the future vfs layer,
30 it will be possible to have tree views over virtual file systems.
34 #include <sys/types.h>
39 #include <sys/param.h>
46 #include "treestore.h"
47 #include "../vfs/vfs.h"
54 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
58 void (*tree_store_dirty_notify
)(int state
) = NULL
;
61 tree_store_dirty (int state
)
65 if (tree_store_dirty_notify
)
66 (*tree_store_dirty_notify
)(state
);
69 /* Returns number of common characters */
70 static int str_common (char *s1
, char *s2
)
74 while (*s1
++ == *s2
++)
79 /* The directory names are arranged in a single linked list in the same
80 order as they are displayed. When the tree is displayed the expected
91 i.e. the required collating sequence when comparing two directory names is
92 '\0' < PATH_SEP < all-other-characters-in-encoding-order
94 Since strcmp doesn't fulfil this requirement we use pathcmp when
95 inserting directory names into the list. The meaning of the return value
96 of pathcmp and strcmp are the same (an integer less than, equal to, or
97 greater than zero if p1 is found to be less than, to match, or be greater
101 pathcmp (const char *p1
, const char *p2
)
103 for ( ;*p1
== *p2
; p1
++, p2
++)
118 /* Searches for specified directory */
120 tree_store_whereis (char *name
)
122 tree_entry
*current
= ts
.tree_first
;
125 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
126 current
= current
->next
;
135 tree_store_get (void)
141 decode (char *buffer
)
143 char *res
= g_strdup (buffer
);
146 for (p
= q
= res
; *p
; p
++, q
++){
173 /* Loads the tree store from the specified filename */
175 tree_store_load_from (char *name
)
178 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
183 g_return_val_if_fail (name
!= NULL
, FALSE
);
188 file
= fopen (name
, "r");
191 fgets (buffer
, sizeof (buffer
), file
);
193 if (strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0){
204 /* File open -> read contents */
206 while (fgets (buffer
, MC_MAXPATHLEN
, file
)){
211 /* Skip invalid records */
212 if ((buffer
[0] != '0' && buffer
[0] != '1'))
215 if (buffer
[1] != ':')
218 scanned
= buffer
[0] == '1';
220 name
= decode (buffer
+2);
224 /* .ado: Drives for NT and OS/2 */
229 tree_store_add_entry (name
);
230 strcpy (oldname
, name
);
234 if (name
[0] != PATH_SEP
){
235 /* Clear-text decompression */
236 char *s
= strtok (name
, " ");
240 different
= strtok (NULL
, "");
242 strcpy (oldname
+ common
, different
);
243 if (vfs_file_is_local (oldname
)){
244 e
= tree_store_add_entry (oldname
);
245 e
->scanned
= scanned
;
250 if (vfs_file_is_local (name
)){
251 e
= tree_store_add_entry (name
);
252 e
->scanned
= scanned
;
254 strcpy (oldname
, name
);
261 /* Nothing loaded, we add some standard directories */
263 tree_store_add_entry (PATH_SEP_STR
);
264 tree_store_rescan (PATH_SEP_STR
);
275 * Loads the tree from the default location.
277 * Return value: TRUE if success, FALSE otherwise.
280 tree_store_load (void)
285 name
= concat_dir_and_file (home_dir
, MC_TREE
);
286 retval
= tree_store_load_from (name
);
293 encode (char *string
)
299 for (special_chars
= 0, p
= string
; *p
; p
++){
300 if (*p
== '\n' || *p
== '\\')
304 res
= g_malloc (p
- string
+ special_chars
+ 1);
305 for (p
= string
, q
= res
; *p
; p
++, q
++){
306 if (*p
!= '\n' && *p
!= '\\'){
327 /* Saves the tree to the specified filename */
329 tree_store_save_to (char *name
)
334 file
= fopen (name
, "w");
338 fprintf (file
, "%s\n", TREE_SIGNATURE
);
340 current
= ts
.tree_first
;
344 if (vfs_file_is_local (current
->name
)){
345 /* Clear-text compression */
347 && (common
= str_common (current
->prev
->name
, current
->name
)) > 2){
348 char *encoded
= encode (current
->name
+ common
);
350 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
, common
, encoded
);
353 char *encoded
= encode (current
->name
);
355 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
360 fprintf (stderr
, _("Can't write to the %s file:\n%s\n"), name
,
361 unix_error_string (errno
));
365 current
= current
->next
;
367 tree_store_dirty (FALSE
);
377 * Saves the tree to the default file in an atomic fashion.
379 * Return value: 0 if success, errno on error.
382 tree_store_save (void)
388 tmp
= concat_dir_and_file (home_dir
, MC_TREE_TMP
);
389 retval
= tree_store_save_to (tmp
);
396 name
= concat_dir_and_file (home_dir
, MC_TREE
);
397 retval
= rename (tmp
, name
);
409 tree_store_add_entry (char *name
)
412 tree_entry
*current
= ts
.tree_first
;
413 tree_entry
*old
= NULL
;
418 if (ts
.tree_last
&& ts
.tree_last
->next
)
421 /* Search for the correct place */
422 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0){
424 current
= current
->next
;
428 return current
; /* Already in the list */
430 /* Not in the list -> add it */
431 new = g_new0 (tree_entry
, 1);
433 /* Append to the end of the list */
445 /* Insert in to the middle of the list */
448 /* Yes, in the middle */
449 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
= g_strdup (name
);
461 len
= strlen (new->name
);
463 for (i
= 0; i
< len
; i
++)
464 if (new->name
[i
] == PATH_SEP
){
466 new->subname
= new->name
+ i
+ 1;
469 submask
= new->next
->submask
;
472 submask
|= 1 << new->sublevel
;
473 submask
&= (2 << new->sublevel
) - 1;
474 new->submask
= submask
;
477 /* Correct the submasks of the previous entries */
479 while (current
&& current
->sublevel
> new->sublevel
){
480 current
->submask
|= 1 << new->sublevel
;
481 current
= current
->prev
;
484 /* The entry has now been added */
486 if (new->sublevel
> 1){
487 /* Let's check if the parent directory is in the tree */
488 char *parent
= g_strdup (new->name
);
491 for (i
= strlen (parent
) - 1; i
> 1; i
--){
492 if (parent
[i
] == PATH_SEP
){
494 tree_store_add_entry (parent
);
501 tree_store_dirty (TRUE
);
506 remove_entry (tree_entry
*entry
)
508 tree_entry
*current
= entry
->prev
;
510 tree_entry
*ret
= NULL
;
512 tree_store_notify_remove (entry
);
514 /* Correct the submasks of the previous entries */
516 submask
= entry
->next
->submask
;
517 while (current
&& current
->sublevel
> entry
->sublevel
){
518 submask
|= 1 << current
->sublevel
;
519 submask
&= (2 << current
->sublevel
) - 1;
520 current
->submask
= submask
;
521 current
= current
->prev
;
524 /* Unlink the entry from the list */
526 entry
->prev
->next
= entry
->next
;
528 ts
.tree_first
= entry
->next
;
531 entry
->next
->prev
= entry
->prev
;
533 ts
.tree_last
= entry
->prev
;
535 /* Free the memory used by the entry */
536 g_free (entry
->name
);
543 tree_store_remove_entry (char *name
)
545 tree_entry
*current
, *base
, *old
;
546 int len
, base_sublevel
;
548 g_return_if_fail (name
!= NULL
);
549 g_return_if_fail (ts
.check_name
!= NULL
);
551 /* Miguel Ugly hack */
552 if (name
[0] == PATH_SEP
&& name
[1] == 0)
554 /* Miguel Ugly hack end */
556 base
= tree_store_whereis (name
);
558 return; /* Doesn't exist */
560 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
561 base_sublevel
= base
->sublevel
;
563 base_sublevel
= base
->sublevel
+ 1;
565 len
= strlen (base
->name
);
566 current
= base
->next
;
568 && strncmp (current
->name
, base
->name
, len
) == 0
569 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
)) {
571 current
= current
->next
;
575 tree_store_dirty (TRUE
);
580 /* This subdirectory exists -> clear deletion mark */
582 tree_store_mark_checked (const char *subname
)
585 tree_entry
*current
, *base
;
590 if (ts
.check_name
== NULL
)
593 /* Calculate the full name of the subdirectory */
594 if (subname
[0] == '.' &&
595 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
597 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
598 name
= g_strconcat (PATH_SEP_STR
, subname
, NULL
);
600 name
= concat_dir_and_file (ts
.check_name
, subname
);
602 /* Search for the subdirectory */
603 current
= ts
.check_start
;
604 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
605 current
= current
->next
;
608 /* Doesn't exist -> add it */
609 current
= tree_store_add_entry (name
);
610 ts
.add_queue
= g_list_prepend (ts
.add_queue
, g_strdup (name
));
614 /* Clear the deletion mark from the subdirectory and its children */
617 len
= strlen (base
->name
);
619 current
= base
->next
;
621 && strncmp (current
->name
, base
->name
, len
) == 0
622 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1)){
624 current
= current
->next
;
629 /* Mark the subdirectories of the current directory for delete */
631 tree_store_start_check (char *path
)
633 tree_entry
*current
, *retval
;
639 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
640 ts
.check_start
= NULL
;
642 tree_store_set_freeze (TRUE
);
644 /* Search for the start of subdirectories */
645 current
= tree_store_whereis (path
);
649 if (mc_stat (path
, &s
) == -1)
652 if (!S_ISDIR (s
.st_mode
))
655 current
= tree_store_add_entry (path
);
656 ts
.check_name
= g_strdup (path
);
661 ts
.check_name
= g_strdup (path
);
665 /* Mark old subdirectories for delete */
666 ts
.check_start
= current
->next
;
667 len
= strlen (ts
.check_name
);
669 current
= ts
.check_start
;
671 && strncmp (current
->name
, ts
.check_name
, len
) == 0
672 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1)){
674 current
= current
->next
;
681 tree_store_start_check_cwd (void)
683 char buffer
[MC_MAXPATHLEN
];
685 mc_get_current_wd (buffer
, MC_MAXPATHLEN
);
686 return tree_store_start_check (buffer
);
689 /* Delete subdirectories which still have the deletion mark */
691 tree_store_end_check (void)
693 tree_entry
*current
, *old
;
695 GList
*the_queue
, *l
;
700 g_return_if_fail (ts
.check_name
!= NULL
);
702 /* Check delete marks and delete if found */
703 len
= strlen (ts
.check_name
);
705 current
= ts
.check_start
;
707 && strncmp (current
->name
, ts
.check_name
, len
) == 0
708 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1)){
710 current
= current
->next
;
715 /* get the stuff in the scan order */
716 ts
.add_queue
= g_list_reverse (ts
.add_queue
);
717 the_queue
= ts
.add_queue
;
719 g_free (ts
.check_name
);
720 ts
.check_name
= NULL
;
722 for (l
= the_queue
; l
; l
= l
->next
){
723 tree_store_notify_add (l
->data
);
727 g_list_free (the_queue
);
729 tree_store_set_freeze (FALSE
);
733 process_special_dirs (GList
**special_dirs
, char *file
)
736 char *buffer
= g_malloc (4096);
739 GetPrivateProfileString ("Special dirs", "list",
740 "", buffer
, 4096, file
);
742 while ((token
= strtok (s
, ",")) != NULL
){
743 *special_dirs
= g_list_prepend (*special_dirs
, g_strdup (token
));
750 should_skip_directory (char *dir
)
752 static GList
*special_dirs
;
759 process_special_dirs (&special_dirs
, profile_name
);
760 process_special_dirs (&special_dirs
, CONFDIR
"mc.global");
763 for (l
= special_dirs
; l
; l
= l
->next
){
764 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
771 tree_store_rescan (char *dir
)
778 if (should_skip_directory (dir
)){
779 entry
= tree_store_add_entry (dir
);
785 entry
= tree_store_start_check (dir
);
790 dirp
= mc_opendir (dir
);
792 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
)){
795 if (dp
->d_name
[0] == '.'){
796 if (dp
->d_name
[1] == 0
797 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
801 full_name
= concat_dir_and_file (dir
, dp
->d_name
);
802 if (mc_lstat (full_name
, &buf
) != -1){
803 if (S_ISDIR (buf
.st_mode
))
804 tree_store_mark_checked (dp
->d_name
);
810 tree_store_end_check ();
816 static Hook
*remove_entry_hooks
;
817 static Hook
*add_entry_hooks
;
818 static Hook
*freeze_hooks
;
821 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
823 add_hook (&remove_entry_hooks
, (void (*)(void *))callback
, data
);
827 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
829 delete_hook (&remove_entry_hooks
, (void (*)(void *))callback
);
833 tree_store_notify_remove (tree_entry
*entry
)
835 Hook
*p
= remove_entry_hooks
;
836 tree_store_remove_fn r
;
840 r
= (tree_store_remove_fn
) p
->hook_fn
;
841 r (entry
, p
->hook_data
);
846 tree_store_add_entry_add_hook (tree_store_add_fn callback
, void *data
)
848 add_hook (&add_entry_hooks
, (void (*)(void *))callback
, data
);
852 tree_store_remove_entry_add_hook (tree_store_add_fn callback
)
854 delete_hook (&add_entry_hooks
, (void (*)(void *))callback
);
858 tree_store_notify_add (char *directory
)
860 Hook
*p
= add_entry_hooks
;
864 r
= (tree_store_add_fn
) p
->hook_fn
;
865 r (directory
, p
->hook_data
);
871 tree_store_add_freeze_hook (tree_freeze_fn callback
, void *data
)
873 add_hook (&freeze_hooks
, (void (*)(void *)) callback
, data
);
877 tree_store_remove_freeze_hook (tree_freeze_fn callback
)
879 delete_hook (&freeze_hooks
, (void (*)(void *))callback
);
883 tree_store_set_freeze (int freeze
)
885 Hook
*p
= freeze_hooks
;
889 f
= (tree_freeze_fn
) p
->hook_fn
;
890 f (freeze
, p
->hook_data
);
896 tree_store_opendir (char *path
)
901 entry
= tree_store_whereis (path
);
902 if (!entry
|| (entry
&& !entry
->scanned
)) {
903 entry
= tree_store_rescan (path
);
909 if (entry
->next
== NULL
)
912 scan
= g_new (tree_scan
, 1);
914 scan
->current
= entry
->next
;
915 scan
->sublevel
= entry
->next
->sublevel
;
917 scan
->base_dir_len
= strlen (path
);
922 tree_store_readdir (tree_scan
*scan
)
927 g_assert (scan
!= NULL
);
929 len
= scan
->base_dir_len
;
930 entry
= scan
->current
;
932 (strncmp (entry
->name
, scan
->base
->name
, len
) == 0) &&
933 (entry
->name
[len
] == 0 || entry
->name
[len
] == PATH_SEP
|| len
== 1)){
935 if (entry
->sublevel
== scan
->sublevel
){
936 scan
->current
= entry
->next
;
946 tree_store_closedir (tree_scan
*scanner
)
948 g_assert (scanner
!= NULL
);