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>
45 #include "treestore.h"
46 #include "../vfs/vfs.h"
50 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
54 void (*tree_store_dirty_notify
)(int state
) = NULL
;
57 tree_store_dirty (int state
)
61 if (tree_store_dirty_notify
)
62 (*tree_store_dirty_notify
)(state
);
65 /* Returns number of common characters */
66 static int str_common (char *s1
, char *s2
)
70 while (*s1
++ == *s2
++)
75 /* The directory names are arranged in a single linked list in the same
76 order as they are displayed. When the tree is displayed the expected
87 i.e. the required collating sequence when comparing two directory names is
88 '\0' < PATH_SEP < all-other-characters-in-encoding-order
90 Since strcmp doesn't fulfil this requirement we use pathcmp when
91 inserting directory names into the list. The meaning of the return value
92 of pathcmp and strcmp are the same (an integer less than, equal to, or
93 greater than zero if p1 is found to be less than, to match, or be greater
97 pathcmp (const char *p1
, const char *p2
)
99 for ( ;*p1
== *p2
; p1
++, p2
++)
114 /* Searches for specified directory */
116 tree_store_whereis (char *name
)
118 tree_entry
*current
= ts
.tree_first
;
121 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
122 current
= current
->next
;
131 tree_store_get (void)
137 decode (char *buffer
)
139 char *res
= g_strdup (buffer
);
142 for (p
= q
= res
; *p
; p
++, q
++){
169 /* Loads the tree store from the specified filename */
171 tree_store_load_from (char *name
)
174 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
179 g_return_val_if_fail (name
!= NULL
, FALSE
);
184 file
= fopen (name
, "r");
187 fgets (buffer
, sizeof (buffer
), file
);
189 if (strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0){
200 /* File open -> read contents */
202 while (fgets (buffer
, MC_MAXPATHLEN
, file
)){
207 /* Skip invalid records */
208 if ((buffer
[0] != '0' && buffer
[0] != '1'))
211 if (buffer
[1] != ':')
214 scanned
= buffer
[0] == '1';
216 name
= decode (buffer
+2);
220 /* .ado: Drives for NT and OS/2 */
225 tree_store_add_entry (name
);
226 strcpy (oldname
, name
);
230 if (name
[0] != PATH_SEP
){
231 /* Clear-text decompression */
232 char *s
= strtok (name
, " ");
236 different
= strtok (NULL
, "");
238 strcpy (oldname
+ common
, different
);
239 if (vfs_file_is_local (oldname
)){
240 e
= tree_store_add_entry (oldname
);
241 e
->scanned
= scanned
;
246 if (vfs_file_is_local (name
)){
247 e
= tree_store_add_entry (name
);
248 e
->scanned
= scanned
;
250 strcpy (oldname
, name
);
257 /* Nothing loaded, we add some standard directories */
259 tree_store_add_entry (PATH_SEP_STR
);
260 tree_store_rescan (PATH_SEP_STR
);
271 * Loads the tree from the default location.
273 * Return value: TRUE if success, FALSE otherwise.
276 tree_store_load (void)
281 name
= concat_dir_and_file (home_dir
, MC_TREE
);
282 retval
= tree_store_load_from (name
);
289 encode (char *string
)
295 for (special_chars
= 0, p
= string
; *p
; p
++){
296 if (*p
== '\n' || *p
== '\\')
300 res
= g_malloc (p
- string
+ special_chars
+ 1);
301 for (p
= string
, q
= res
; *p
; p
++, q
++){
302 if (*p
!= '\n' && *p
!= '\\'){
323 /* Saves the tree to the specified filename */
325 tree_store_save_to (char *name
)
330 file
= fopen (name
, "w");
334 fprintf (file
, "%s\n", TREE_SIGNATURE
);
336 current
= ts
.tree_first
;
340 if (vfs_file_is_local (current
->name
)){
341 /* Clear-text compression */
343 && (common
= str_common (current
->prev
->name
, current
->name
)) > 2){
344 char *encoded
= encode (current
->name
+ common
);
346 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
, common
, encoded
);
349 char *encoded
= encode (current
->name
);
351 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
356 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"), name
,
357 unix_error_string (errno
));
361 current
= current
->next
;
363 tree_store_dirty (FALSE
);
373 * Saves the tree to the default file in an atomic fashion.
375 * Return value: 0 if success, errno on error.
378 tree_store_save (void)
384 tmp
= concat_dir_and_file (home_dir
, MC_TREE_TMP
);
385 retval
= tree_store_save_to (tmp
);
392 name
= concat_dir_and_file (home_dir
, MC_TREE
);
393 retval
= rename (tmp
, name
);
405 tree_store_add_entry (char *name
)
408 tree_entry
*current
= ts
.tree_first
;
409 tree_entry
*old
= NULL
;
414 if (ts
.tree_last
&& ts
.tree_last
->next
)
417 /* Search for the correct place */
418 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0){
420 current
= current
->next
;
424 return current
; /* Already in the list */
426 /* Not in the list -> add it */
427 new = g_new0 (tree_entry
, 1);
429 /* Append to the end of the list */
441 /* Insert in to the middle of the list */
444 /* Yes, in the middle */
445 new->next
= old
->next
;
448 /* Nope, in the beginning of the list */
449 new->next
= ts
.tree_first
;
452 new->next
->prev
= new;
455 /* Calculate attributes */
456 new->name
= g_strdup (name
);
457 len
= strlen (new->name
);
459 for (i
= 0; i
< len
; i
++)
460 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
){
476 current
->submask
|= 1 << new->sublevel
;
477 current
= current
->prev
;
480 /* The entry has now been added */
482 if (new->sublevel
> 1){
483 /* Let's check if the parent directory is in the tree */
484 char *parent
= g_strdup (new->name
);
487 for (i
= strlen (parent
) - 1; i
> 1; i
--){
488 if (parent
[i
] == PATH_SEP
){
490 tree_store_add_entry (parent
);
497 tree_store_dirty (TRUE
);
502 remove_entry (tree_entry
*entry
)
504 tree_entry
*current
= entry
->prev
;
506 tree_entry
*ret
= NULL
;
508 tree_store_notify_remove (entry
);
510 /* Correct the submasks of the previous entries */
512 submask
= entry
->next
->submask
;
513 while (current
&& current
->sublevel
> entry
->sublevel
){
514 submask
|= 1 << current
->sublevel
;
515 submask
&= (2 << current
->sublevel
) - 1;
516 current
->submask
= submask
;
517 current
= current
->prev
;
520 /* Unlink the entry from the list */
522 entry
->prev
->next
= entry
->next
;
524 ts
.tree_first
= entry
->next
;
527 entry
->next
->prev
= entry
->prev
;
529 ts
.tree_last
= entry
->prev
;
531 /* Free the memory used by the entry */
532 g_free (entry
->name
);
539 tree_store_remove_entry (char *name
)
541 tree_entry
*current
, *base
, *old
;
542 int len
, base_sublevel
;
544 g_return_if_fail (name
!= NULL
);
545 g_return_if_fail (ts
.check_name
!= NULL
);
547 /* Miguel Ugly hack */
548 if (name
[0] == PATH_SEP
&& name
[1] == 0)
550 /* Miguel Ugly hack end */
552 base
= tree_store_whereis (name
);
554 return; /* Doesn't exist */
556 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
557 base_sublevel
= base
->sublevel
;
559 base_sublevel
= base
->sublevel
+ 1;
561 len
= strlen (base
->name
);
562 current
= base
->next
;
564 && strncmp (current
->name
, base
->name
, len
) == 0
565 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
)) {
567 current
= current
->next
;
571 tree_store_dirty (TRUE
);
576 /* This subdirectory exists -> clear deletion mark */
578 tree_store_mark_checked (const char *subname
)
581 tree_entry
*current
, *base
;
586 if (ts
.check_name
== NULL
)
589 /* Calculate the full name of the subdirectory */
590 if (subname
[0] == '.' &&
591 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
593 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
594 name
= g_strconcat (PATH_SEP_STR
, subname
, NULL
);
596 name
= concat_dir_and_file (ts
.check_name
, subname
);
598 /* Search for the subdirectory */
599 current
= ts
.check_start
;
600 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
601 current
= current
->next
;
604 /* Doesn't exist -> add it */
605 current
= tree_store_add_entry (name
);
606 ts
.add_queue
= g_list_prepend (ts
.add_queue
, g_strdup (name
));
610 /* Clear the deletion mark from the subdirectory and its children */
613 len
= strlen (base
->name
);
615 current
= base
->next
;
617 && strncmp (current
->name
, base
->name
, len
) == 0
618 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1)){
620 current
= current
->next
;
625 /* Mark the subdirectories of the current directory for delete */
627 tree_store_start_check (char *path
)
629 tree_entry
*current
, *retval
;
635 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
636 ts
.check_start
= NULL
;
638 tree_store_set_freeze (TRUE
);
640 /* Search for the start of subdirectories */
641 current
= tree_store_whereis (path
);
645 if (mc_stat (path
, &s
) == -1)
648 if (!S_ISDIR (s
.st_mode
))
651 current
= tree_store_add_entry (path
);
652 ts
.check_name
= g_strdup (path
);
657 ts
.check_name
= g_strdup (path
);
661 /* Mark old subdirectories for delete */
662 ts
.check_start
= current
->next
;
663 len
= strlen (ts
.check_name
);
665 current
= ts
.check_start
;
667 && strncmp (current
->name
, ts
.check_name
, len
) == 0
668 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1)){
670 current
= current
->next
;
677 tree_store_start_check_cwd (void)
679 char buffer
[MC_MAXPATHLEN
];
681 mc_get_current_wd (buffer
, MC_MAXPATHLEN
);
682 return tree_store_start_check (buffer
);
685 /* Delete subdirectories which still have the deletion mark */
687 tree_store_end_check (void)
689 tree_entry
*current
, *old
;
691 GList
*the_queue
, *l
;
696 g_return_if_fail (ts
.check_name
!= NULL
);
698 /* Check delete marks and delete if found */
699 len
= strlen (ts
.check_name
);
701 current
= ts
.check_start
;
703 && strncmp (current
->name
, ts
.check_name
, len
) == 0
704 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1)){
706 current
= current
->next
;
711 /* get the stuff in the scan order */
712 ts
.add_queue
= g_list_reverse (ts
.add_queue
);
713 the_queue
= ts
.add_queue
;
715 g_free (ts
.check_name
);
716 ts
.check_name
= NULL
;
718 for (l
= the_queue
; l
; l
= l
->next
){
719 tree_store_notify_add (l
->data
);
723 g_list_free (the_queue
);
725 tree_store_set_freeze (FALSE
);
729 process_special_dirs (GList
**special_dirs
, char *file
)
732 char *buffer
= g_malloc (4096);
735 GetPrivateProfileString ("Special dirs", "list",
736 "", buffer
, 4096, file
);
738 while ((token
= strtok (s
, ",")) != NULL
){
739 *special_dirs
= g_list_prepend (*special_dirs
, g_strdup (token
));
746 should_skip_directory (char *dir
)
748 static GList
*special_dirs
;
755 process_special_dirs (&special_dirs
, profile_name
);
756 process_special_dirs (&special_dirs
, CONFDIR
"mc.global");
759 for (l
= special_dirs
; l
; l
= l
->next
){
760 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
767 tree_store_rescan (char *dir
)
774 if (should_skip_directory (dir
)){
775 entry
= tree_store_add_entry (dir
);
781 entry
= tree_store_start_check (dir
);
786 dirp
= mc_opendir (dir
);
788 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
)){
791 if (dp
->d_name
[0] == '.'){
792 if (dp
->d_name
[1] == 0
793 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
797 full_name
= concat_dir_and_file (dir
, dp
->d_name
);
798 if (mc_lstat (full_name
, &buf
) != -1){
799 if (S_ISDIR (buf
.st_mode
))
800 tree_store_mark_checked (dp
->d_name
);
806 tree_store_end_check ();
812 static Hook
*remove_entry_hooks
;
813 static Hook
*add_entry_hooks
;
814 static Hook
*freeze_hooks
;
817 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
819 add_hook (&remove_entry_hooks
, (void (*)(void *))callback
, data
);
823 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
825 delete_hook (&remove_entry_hooks
, (void (*)(void *))callback
);
829 tree_store_notify_remove (tree_entry
*entry
)
831 Hook
*p
= remove_entry_hooks
;
832 tree_store_remove_fn r
;
836 r
= (tree_store_remove_fn
) p
->hook_fn
;
837 r (entry
, p
->hook_data
);
842 tree_store_add_entry_add_hook (tree_store_add_fn callback
, void *data
)
844 add_hook (&add_entry_hooks
, (void (*)(void *))callback
, data
);
848 tree_store_remove_entry_add_hook (tree_store_add_fn callback
)
850 delete_hook (&add_entry_hooks
, (void (*)(void *))callback
);
854 tree_store_notify_add (char *directory
)
856 Hook
*p
= add_entry_hooks
;
860 r
= (tree_store_add_fn
) p
->hook_fn
;
861 r (directory
, p
->hook_data
);
867 tree_store_add_freeze_hook (tree_freeze_fn callback
, void *data
)
869 add_hook (&freeze_hooks
, (void (*)(void *)) callback
, data
);
873 tree_store_remove_freeze_hook (tree_freeze_fn callback
)
875 delete_hook (&freeze_hooks
, (void (*)(void *))callback
);
879 tree_store_set_freeze (int freeze
)
881 Hook
*p
= freeze_hooks
;
885 f
= (tree_freeze_fn
) p
->hook_fn
;
886 f (freeze
, p
->hook_data
);
892 tree_store_opendir (char *path
)
897 entry
= tree_store_whereis (path
);
898 if (!entry
|| (entry
&& !entry
->scanned
)) {
899 entry
= tree_store_rescan (path
);
905 if (entry
->next
== NULL
)
908 scan
= g_new (tree_scan
, 1);
910 scan
->current
= entry
->next
;
911 scan
->sublevel
= entry
->next
->sublevel
;
913 scan
->base_dir_len
= strlen (path
);
918 tree_store_readdir (tree_scan
*scan
)
923 g_assert (scan
!= NULL
);
925 len
= scan
->base_dir_len
;
926 entry
= scan
->current
;
928 (strncmp (entry
->name
, scan
->base
->name
, len
) == 0) &&
929 (entry
->name
[len
] == 0 || entry
->name
[len
] == PATH_SEP
|| len
== 1)){
931 if (entry
->sublevel
== scan
->sublevel
){
932 scan
->current
= entry
->next
;
942 tree_store_closedir (tree_scan
*scanner
)
944 g_assert (scanner
!= NULL
);