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>
46 #include "treestore.h"
47 #include "../vfs/vfs.h"
51 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
55 void (*tree_store_dirty_notify
)(int state
) = NULL
;
58 tree_store_dirty (int state
)
62 if (tree_store_dirty_notify
)
63 (*tree_store_dirty_notify
)(state
);
66 /* Returns number of common characters */
67 static int str_common (char *s1
, char *s2
)
71 while (*s1
++ == *s2
++)
76 /* The directory names are arranged in a single linked list in the same
77 order as they are displayed. When the tree is displayed the expected
88 i.e. the required collating sequence when comparing two directory names is
89 '\0' < PATH_SEP < all-other-characters-in-encoding-order
91 Since strcmp doesn't fulfil this requirement we use pathcmp when
92 inserting directory names into the list. The meaning of the return value
93 of pathcmp and strcmp are the same (an integer less than, equal to, or
94 greater than zero if p1 is found to be less than, to match, or be greater
98 pathcmp (const char *p1
, const char *p2
)
100 for ( ;*p1
== *p2
; p1
++, p2
++)
115 /* Searches for specified directory */
117 tree_store_whereis (char *name
)
119 tree_entry
*current
= ts
.tree_first
;
122 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
123 current
= current
->next
;
132 tree_store_get (void)
138 decode (char *buffer
)
140 char *res
= g_strdup (buffer
);
143 for (p
= q
= res
; *p
; p
++, q
++){
170 /* Loads the tree store from the specified filename */
172 tree_store_load_from (char *name
)
175 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
180 g_return_val_if_fail (name
!= NULL
, FALSE
);
185 file
= fopen (name
, "r");
188 fgets (buffer
, sizeof (buffer
), file
);
190 if (strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0){
201 /* File open -> read contents */
203 while (fgets (buffer
, MC_MAXPATHLEN
, file
)){
208 /* Skip invalid records */
209 if ((buffer
[0] != '0' && buffer
[0] != '1'))
212 if (buffer
[1] != ':')
215 scanned
= buffer
[0] == '1';
217 name
= decode (buffer
+2);
221 /* .ado: Drives for NT and OS/2 */
226 tree_store_add_entry (name
);
227 strcpy (oldname
, name
);
231 if (name
[0] != PATH_SEP
){
232 /* Clear-text decompression */
233 char *s
= strtok (name
, " ");
237 different
= strtok (NULL
, "");
239 strcpy (oldname
+ common
, different
);
240 if (vfs_file_is_local (oldname
)){
241 e
= tree_store_add_entry (oldname
);
242 e
->scanned
= scanned
;
247 if (vfs_file_is_local (name
)){
248 e
= tree_store_add_entry (name
);
249 e
->scanned
= scanned
;
251 strcpy (oldname
, name
);
258 /* Nothing loaded, we add some standard directories */
260 tree_store_add_entry (PATH_SEP_STR
);
261 tree_store_rescan (PATH_SEP_STR
);
272 * Loads the tree from the default location.
274 * Return value: TRUE if success, FALSE otherwise.
277 tree_store_load (void)
282 name
= concat_dir_and_file (home_dir
, MC_TREE
);
283 retval
= tree_store_load_from (name
);
290 encode (char *string
)
296 for (special_chars
= 0, p
= string
; *p
; p
++){
297 if (*p
== '\n' || *p
== '\\')
301 res
= g_malloc (p
- string
+ special_chars
+ 1);
302 for (p
= string
, q
= res
; *p
; p
++, q
++){
303 if (*p
!= '\n' && *p
!= '\\'){
324 /* Saves the tree to the specified filename */
326 tree_store_save_to (char *name
)
331 file
= fopen (name
, "w");
335 fprintf (file
, "%s\n", TREE_SIGNATURE
);
337 current
= ts
.tree_first
;
341 if (vfs_file_is_local (current
->name
)){
342 /* Clear-text compression */
344 && (common
= str_common (current
->prev
->name
, current
->name
)) > 2){
345 char *encoded
= encode (current
->name
+ common
);
347 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
, common
, encoded
);
350 char *encoded
= encode (current
->name
);
352 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
357 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"), name
,
358 unix_error_string (errno
));
362 current
= current
->next
;
364 tree_store_dirty (FALSE
);
374 * Saves the tree to the default file in an atomic fashion.
376 * Return value: 0 if success, errno on error.
379 tree_store_save (void)
385 tmp
= concat_dir_and_file (home_dir
, MC_TREE_TMP
);
386 retval
= tree_store_save_to (tmp
);
393 name
= concat_dir_and_file (home_dir
, MC_TREE
);
394 retval
= rename (tmp
, name
);
406 tree_store_add_entry (char *name
)
409 tree_entry
*current
= ts
.tree_first
;
410 tree_entry
*old
= NULL
;
415 if (ts
.tree_last
&& ts
.tree_last
->next
)
418 /* Search for the correct place */
419 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0){
421 current
= current
->next
;
425 return current
; /* Already in the list */
427 /* Not in the list -> add it */
428 new = g_new0 (tree_entry
, 1);
430 /* Append to the end of the list */
442 /* Insert in to the middle of the list */
445 /* Yes, in the middle */
446 new->next
= old
->next
;
449 /* Nope, in the beginning of the list */
450 new->next
= ts
.tree_first
;
453 new->next
->prev
= new;
456 /* Calculate attributes */
457 new->name
= g_strdup (name
);
458 len
= strlen (new->name
);
460 for (i
= 0; i
< len
; i
++)
461 if (new->name
[i
] == PATH_SEP
){
463 new->subname
= new->name
+ i
+ 1;
466 submask
= new->next
->submask
;
469 submask
|= 1 << new->sublevel
;
470 submask
&= (2 << new->sublevel
) - 1;
471 new->submask
= submask
;
474 /* Correct the submasks of the previous entries */
476 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){
484 /* Let's check if the parent directory is in the tree */
485 char *parent
= g_strdup (new->name
);
488 for (i
= strlen (parent
) - 1; i
> 1; i
--){
489 if (parent
[i
] == PATH_SEP
){
491 tree_store_add_entry (parent
);
498 tree_store_dirty (TRUE
);
503 remove_entry (tree_entry
*entry
)
505 tree_entry
*current
= entry
->prev
;
507 tree_entry
*ret
= NULL
;
509 tree_store_notify_remove (entry
);
511 /* Correct the submasks of the previous entries */
513 submask
= entry
->next
->submask
;
514 while (current
&& current
->sublevel
> entry
->sublevel
){
515 submask
|= 1 << current
->sublevel
;
516 submask
&= (2 << current
->sublevel
) - 1;
517 current
->submask
= submask
;
518 current
= current
->prev
;
521 /* Unlink the entry from the list */
523 entry
->prev
->next
= entry
->next
;
525 ts
.tree_first
= entry
->next
;
528 entry
->next
->prev
= entry
->prev
;
530 ts
.tree_last
= entry
->prev
;
532 /* Free the memory used by the entry */
533 g_free (entry
->name
);
540 tree_store_remove_entry (char *name
)
542 tree_entry
*current
, *base
, *old
;
543 int len
, base_sublevel
;
545 g_return_if_fail (name
!= NULL
);
546 g_return_if_fail (ts
.check_name
!= NULL
);
548 /* Miguel Ugly hack */
549 if (name
[0] == PATH_SEP
&& name
[1] == 0)
551 /* Miguel Ugly hack end */
553 base
= tree_store_whereis (name
);
555 return; /* Doesn't exist */
557 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
558 base_sublevel
= base
->sublevel
;
560 base_sublevel
= base
->sublevel
+ 1;
562 len
= strlen (base
->name
);
563 current
= base
->next
;
565 && strncmp (current
->name
, base
->name
, len
) == 0
566 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
)) {
568 current
= current
->next
;
572 tree_store_dirty (TRUE
);
577 /* This subdirectory exists -> clear deletion mark */
579 tree_store_mark_checked (const char *subname
)
582 tree_entry
*current
, *base
;
587 if (ts
.check_name
== NULL
)
590 /* Calculate the full name of the subdirectory */
591 if (subname
[0] == '.' &&
592 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
594 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
595 name
= g_strconcat (PATH_SEP_STR
, subname
, NULL
);
597 name
= concat_dir_and_file (ts
.check_name
, subname
);
599 /* Search for the subdirectory */
600 current
= ts
.check_start
;
601 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
602 current
= current
->next
;
605 /* Doesn't exist -> add it */
606 current
= tree_store_add_entry (name
);
607 ts
.add_queue
= g_list_prepend (ts
.add_queue
, g_strdup (name
));
611 /* Clear the deletion mark from the subdirectory and its children */
614 len
= strlen (base
->name
);
616 current
= base
->next
;
618 && strncmp (current
->name
, base
->name
, len
) == 0
619 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1)){
621 current
= current
->next
;
626 /* Mark the subdirectories of the current directory for delete */
628 tree_store_start_check (char *path
)
630 tree_entry
*current
, *retval
;
636 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
637 ts
.check_start
= NULL
;
639 tree_store_set_freeze (TRUE
);
641 /* Search for the start of subdirectories */
642 current
= tree_store_whereis (path
);
646 if (mc_stat (path
, &s
) == -1)
649 if (!S_ISDIR (s
.st_mode
))
652 current
= tree_store_add_entry (path
);
653 ts
.check_name
= g_strdup (path
);
658 ts
.check_name
= g_strdup (path
);
662 /* Mark old subdirectories for delete */
663 ts
.check_start
= current
->next
;
664 len
= strlen (ts
.check_name
);
666 current
= ts
.check_start
;
668 && strncmp (current
->name
, ts
.check_name
, len
) == 0
669 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1)){
671 current
= current
->next
;
678 tree_store_start_check_cwd (void)
680 char buffer
[MC_MAXPATHLEN
];
682 mc_get_current_wd (buffer
, MC_MAXPATHLEN
);
683 return tree_store_start_check (buffer
);
686 /* Delete subdirectories which still have the deletion mark */
688 tree_store_end_check (void)
690 tree_entry
*current
, *old
;
692 GList
*the_queue
, *l
;
697 g_return_if_fail (ts
.check_name
!= NULL
);
699 /* Check delete marks and delete if found */
700 len
= strlen (ts
.check_name
);
702 current
= ts
.check_start
;
704 && strncmp (current
->name
, ts
.check_name
, len
) == 0
705 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1)){
707 current
= current
->next
;
712 /* get the stuff in the scan order */
713 ts
.add_queue
= g_list_reverse (ts
.add_queue
);
714 the_queue
= ts
.add_queue
;
716 g_free (ts
.check_name
);
717 ts
.check_name
= NULL
;
719 for (l
= the_queue
; l
; l
= l
->next
){
720 tree_store_notify_add (l
->data
);
724 g_list_free (the_queue
);
726 tree_store_set_freeze (FALSE
);
730 process_special_dirs (GList
**special_dirs
, char *file
)
733 char *buffer
= g_malloc (4096);
736 GetPrivateProfileString ("Special dirs", "list",
737 "", buffer
, 4096, file
);
739 while ((token
= strtok (s
, ",")) != NULL
){
740 *special_dirs
= g_list_prepend (*special_dirs
, g_strdup (token
));
747 should_skip_directory (char *dir
)
749 static GList
*special_dirs
;
756 process_special_dirs (&special_dirs
, profile_name
);
757 process_special_dirs (&special_dirs
, CONFDIR
"mc.global");
760 for (l
= special_dirs
; l
; l
= l
->next
){
761 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
768 tree_store_rescan (char *dir
)
775 if (should_skip_directory (dir
)){
776 entry
= tree_store_add_entry (dir
);
782 entry
= tree_store_start_check (dir
);
787 dirp
= mc_opendir (dir
);
789 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
)){
792 if (dp
->d_name
[0] == '.'){
793 if (dp
->d_name
[1] == 0
794 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
798 full_name
= concat_dir_and_file (dir
, dp
->d_name
);
799 if (mc_lstat (full_name
, &buf
) != -1){
800 if (S_ISDIR (buf
.st_mode
))
801 tree_store_mark_checked (dp
->d_name
);
807 tree_store_end_check ();
813 static Hook
*remove_entry_hooks
;
814 static Hook
*add_entry_hooks
;
815 static Hook
*freeze_hooks
;
818 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
820 add_hook (&remove_entry_hooks
, (void (*)(void *))callback
, data
);
824 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
826 delete_hook (&remove_entry_hooks
, (void (*)(void *))callback
);
830 tree_store_notify_remove (tree_entry
*entry
)
832 Hook
*p
= remove_entry_hooks
;
833 tree_store_remove_fn r
;
837 r
= (tree_store_remove_fn
) p
->hook_fn
;
838 r (entry
, p
->hook_data
);
843 tree_store_add_entry_add_hook (tree_store_add_fn callback
, void *data
)
845 add_hook (&add_entry_hooks
, (void (*)(void *))callback
, data
);
849 tree_store_remove_entry_add_hook (tree_store_add_fn callback
)
851 delete_hook (&add_entry_hooks
, (void (*)(void *))callback
);
855 tree_store_notify_add (char *directory
)
857 Hook
*p
= add_entry_hooks
;
861 r
= (tree_store_add_fn
) p
->hook_fn
;
862 r (directory
, p
->hook_data
);
868 tree_store_add_freeze_hook (tree_freeze_fn callback
, void *data
)
870 add_hook (&freeze_hooks
, (void (*)(void *)) callback
, data
);
874 tree_store_remove_freeze_hook (tree_freeze_fn callback
)
876 delete_hook (&freeze_hooks
, (void (*)(void *))callback
);
880 tree_store_set_freeze (int freeze
)
882 Hook
*p
= freeze_hooks
;
886 f
= (tree_freeze_fn
) p
->hook_fn
;
887 f (freeze
, p
->hook_data
);
893 tree_store_opendir (char *path
)
898 entry
= tree_store_whereis (path
);
899 if (!entry
|| (entry
&& !entry
->scanned
)) {
900 entry
= tree_store_rescan (path
);
906 if (entry
->next
== NULL
)
909 scan
= g_new (tree_scan
, 1);
911 scan
->current
= entry
->next
;
912 scan
->sublevel
= entry
->next
->sublevel
;
914 scan
->base_dir_len
= strlen (path
);
919 tree_store_readdir (tree_scan
*scan
)
924 g_assert (scan
!= NULL
);
926 len
= scan
->base_dir_len
;
927 entry
= scan
->current
;
929 (strncmp (entry
->name
, scan
->base
->name
, len
) == 0) &&
930 (entry
->name
[len
] == 0 || entry
->name
[len
] == PATH_SEP
|| len
== 1)){
932 if (entry
->sublevel
== scan
->sublevel
){
933 scan
->current
= entry
->next
;
943 tree_store_closedir (tree_scan
*scanner
)
945 g_assert (scanner
!= NULL
);