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>
44 #include "treestore.h"
45 #include "../vfs/vfs.h"
49 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
54 tree_store_dirty(int state
)
59 /* Returns number of common characters */
61 str_common(char *s1
, char *s2
)
65 while (*s1
++ == *s2
++)
70 /* The directory names are arranged in a single linked list in the same
71 order as they are displayed. When the tree is displayed the expected
82 i.e. the required collating sequence when comparing two directory names is
83 '\0' < PATH_SEP < all-other-characters-in-encoding-order
85 Since strcmp doesn't fulfil this requirement we use pathcmp when
86 inserting directory names into the list. The meaning of the return value
87 of pathcmp and strcmp are the same (an integer less than, equal to, or
88 greater than zero if p1 is found to be less than, to match, or be greater
92 pathcmp(const char *p1
, const char *p2
)
94 for (; *p1
== *p2
; p1
++, p2
++)
109 /* Searches for specified directory */
111 tree_store_whereis(char *name
)
113 tree_entry
*current
= ts
.tree_first
;
116 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
117 current
= current
->next
;
134 char *res
= g_strdup(buffer
);
137 for (p
= q
= res
; *p
; p
++, q
++) {
164 /* Loads the tree store from the specified filename */
166 tree_store_load_from(char *name
)
169 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
174 g_return_val_if_fail(name
!= NULL
, FALSE
);
179 file
= fopen(name
, "r");
182 fgets(buffer
, sizeof(buffer
), file
);
184 if (strncmp(buffer
, TREE_SIGNATURE
, strlen(TREE_SIGNATURE
)) != 0) {
195 /* File open -> read contents */
197 while (fgets(buffer
, MC_MAXPATHLEN
, file
)) {
202 /* Skip invalid records */
203 if ((buffer
[0] != '0' && buffer
[0] != '1'))
206 if (buffer
[1] != ':')
209 scanned
= buffer
[0] == '1';
211 name
= decode(buffer
+ 2);
215 /* .ado: Drives for Win32 */
218 (name
[1] == ':') && (name
[2] == '\\')) {
219 tree_store_add_entry(name
);
220 strcpy(oldname
, name
);
224 if (name
[0] != PATH_SEP
) {
225 /* Clear-text decompression */
226 char *s
= strtok(name
, " ");
230 different
= strtok(NULL
, "");
232 strcpy(oldname
+ common
, different
);
233 if (vfs_file_is_local(oldname
)) {
234 e
= tree_store_add_entry(oldname
);
235 e
->scanned
= scanned
;
240 if (vfs_file_is_local(name
)) {
241 e
= tree_store_add_entry(name
);
242 e
->scanned
= scanned
;
244 strcpy(oldname
, name
);
251 /* Nothing loaded, we add some standard directories */
252 if (!ts
.tree_first
) {
253 tree_store_add_entry(PATH_SEP_STR
);
254 tree_store_rescan(PATH_SEP_STR
);
265 * Loads the tree from the default location.
267 * Return value: TRUE if success, FALSE otherwise.
270 tree_store_load(void)
275 name
= concat_dir_and_file(home_dir
, MC_TREE
);
276 retval
= tree_store_load_from(name
);
289 for (special_chars
= 0, p
= string
; *p
; p
++) {
290 if (*p
== '\n' || *p
== '\\')
294 res
= g_malloc(p
- string
+ special_chars
+ 1);
295 for (p
= string
, q
= res
; *p
; p
++, q
++) {
296 if (*p
!= '\n' && *p
!= '\\') {
317 /* Saves the tree to the specified filename */
319 tree_store_save_to(char *name
)
324 file
= fopen(name
, "w");
328 fprintf(file
, "%s\n", TREE_SIGNATURE
);
330 current
= ts
.tree_first
;
334 if (vfs_file_is_local(current
->name
)) {
335 /* Clear-text compression */
338 str_common(current
->prev
->name
, current
->name
)) > 2) {
339 char *encoded
= encode(current
->name
+ common
);
341 i
= fprintf(file
, "%d:%d %s\n", current
->scanned
, common
,
345 char *encoded
= encode(current
->name
);
347 i
= fprintf(file
, "%d:%s\n", current
->scanned
, encoded
);
352 fprintf(stderr
, _("Cannot write to the %s file:\n%s\n"),
353 name
, unix_error_string(errno
));
357 current
= current
->next
;
359 tree_store_dirty(FALSE
);
369 * Saves the tree to the default file in an atomic fashion.
371 * Return value: 0 if success, errno on error.
374 tree_store_save(void)
380 tmp
= concat_dir_and_file(home_dir
, MC_TREE_TMP
);
381 retval
= tree_store_save_to(tmp
);
388 name
= concat_dir_and_file(home_dir
, MC_TREE
);
389 retval
= rename(tmp
, name
);
401 tree_store_add_entry(char *name
)
404 tree_entry
*current
= ts
.tree_first
;
405 tree_entry
*old
= NULL
;
410 if (ts
.tree_last
&& ts
.tree_last
->next
)
413 /* Search for the correct place */
414 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0) {
416 current
= current
->next
;
420 return current
; /* Already in the list */
422 /* Not in the list -> add it */
423 new = g_new0(tree_entry
, 1);
425 /* Append to the end of the list */
426 if (!ts
.tree_first
) {
437 /* Insert in to the middle of the list */
440 /* Yes, in the middle */
441 new->next
= old
->next
;
444 /* Nope, in the beginning of the list */
445 new->next
= ts
.tree_first
;
448 new->next
->prev
= new;
451 /* Calculate attributes */
452 new->name
= g_strdup(name
);
453 len
= strlen(new->name
);
455 for (i
= 0; i
< len
; i
++)
456 if (new->name
[i
] == PATH_SEP
) {
458 new->subname
= new->name
+ i
+ 1;
461 submask
= new->next
->submask
;
464 submask
|= 1 << new->sublevel
;
465 submask
&= (2 << new->sublevel
) - 1;
466 new->submask
= submask
;
469 /* Correct the submasks of the previous entries */
471 while (current
&& current
->sublevel
> new->sublevel
) {
472 current
->submask
|= 1 << new->sublevel
;
473 current
= current
->prev
;
476 /* The entry has now been added */
478 if (new->sublevel
> 1) {
479 /* Let's check if the parent directory is in the tree */
480 char *parent
= g_strdup(new->name
);
483 for (i
= strlen(parent
) - 1; i
> 1; i
--) {
484 if (parent
[i
] == PATH_SEP
) {
486 tree_store_add_entry(parent
);
493 tree_store_dirty(TRUE
);
497 static Hook
*remove_entry_hooks
;
500 tree_store_add_entry_remove_hook(tree_store_remove_fn callback
, void *data
)
502 add_hook(&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
506 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback
)
508 delete_hook(&remove_entry_hooks
, (void (*)(void *)) callback
);
512 tree_store_notify_remove(tree_entry
* entry
)
514 Hook
*p
= remove_entry_hooks
;
515 tree_store_remove_fn r
;
518 r
= (tree_store_remove_fn
) p
->hook_fn
;
519 r(entry
, p
->hook_data
);
525 remove_entry(tree_entry
* entry
)
527 tree_entry
*current
= entry
->prev
;
529 tree_entry
*ret
= NULL
;
531 tree_store_notify_remove(entry
);
533 /* Correct the submasks of the previous entries */
535 submask
= entry
->next
->submask
;
536 while (current
&& current
->sublevel
> entry
->sublevel
) {
537 submask
|= 1 << current
->sublevel
;
538 submask
&= (2 << current
->sublevel
) - 1;
539 current
->submask
= submask
;
540 current
= current
->prev
;
543 /* Unlink the entry from the list */
545 entry
->prev
->next
= entry
->next
;
547 ts
.tree_first
= entry
->next
;
550 entry
->next
->prev
= entry
->prev
;
552 ts
.tree_last
= entry
->prev
;
554 /* Free the memory used by the entry */
562 tree_store_remove_entry(char *name
)
564 tree_entry
*current
, *base
, *old
;
567 g_return_if_fail(name
!= NULL
);
569 /* Miguel Ugly hack */
570 if (name
[0] == PATH_SEP
&& name
[1] == 0)
572 /* Miguel Ugly hack end */
574 base
= tree_store_whereis(name
);
576 return; /* Doesn't exist */
578 len
= strlen(base
->name
);
579 current
= base
->next
;
581 && strncmp(current
->name
, base
->name
, len
) == 0
582 && (current
->name
[len
] == '\0'
583 || current
->name
[len
] == PATH_SEP
)) {
585 current
= current
->next
;
589 tree_store_dirty(TRUE
);
594 /* This subdirectory exists -> clear deletion mark */
596 tree_store_mark_checked(const char *subname
)
599 tree_entry
*current
, *base
;
604 if (ts
.check_name
== NULL
)
607 /* Calculate the full name of the subdirectory */
608 if (subname
[0] == '.' &&
609 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
611 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
612 name
= g_strconcat(PATH_SEP_STR
, subname
, NULL
);
614 name
= concat_dir_and_file(ts
.check_name
, subname
);
616 /* Search for the subdirectory */
617 current
= ts
.check_start
;
618 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
619 current
= current
->next
;
622 /* Doesn't exist -> add it */
623 current
= tree_store_add_entry(name
);
624 ts
.add_queue
= g_list_prepend(ts
.add_queue
, g_strdup(name
));
628 /* Clear the deletion mark from the subdirectory and its children */
631 len
= strlen(base
->name
);
633 current
= base
->next
;
635 && strncmp(current
->name
, base
->name
, len
) == 0
636 && (current
->name
[len
] == '\0'
637 || current
->name
[len
] == PATH_SEP
|| len
== 1)) {
639 current
= current
->next
;
644 /* Mark the subdirectories of the current directory for delete */
646 tree_store_start_check(char *path
)
648 tree_entry
*current
, *retval
;
654 g_return_val_if_fail(ts
.check_name
== NULL
, NULL
);
655 ts
.check_start
= NULL
;
657 /* Search for the start of subdirectories */
658 current
= tree_store_whereis(path
);
662 if (mc_stat(path
, &s
) == -1)
665 if (!S_ISDIR(s
.st_mode
))
668 current
= tree_store_add_entry(path
);
669 ts
.check_name
= g_strdup(path
);
674 ts
.check_name
= g_strdup(path
);
678 /* Mark old subdirectories for delete */
679 ts
.check_start
= current
->next
;
680 len
= strlen(ts
.check_name
);
682 current
= ts
.check_start
;
684 && strncmp(current
->name
, ts
.check_name
, len
) == 0
685 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
688 current
= current
->next
;
695 tree_store_start_check_cwd(void)
697 char buffer
[MC_MAXPATHLEN
];
699 mc_get_current_wd(buffer
, MC_MAXPATHLEN
);
700 return tree_store_start_check(buffer
);
703 /* Delete subdirectories which still have the deletion mark */
705 tree_store_end_check(void)
707 tree_entry
*current
, *old
;
709 GList
*the_queue
, *l
;
714 g_return_if_fail(ts
.check_name
!= NULL
);
716 /* Check delete marks and delete if found */
717 len
= strlen(ts
.check_name
);
719 current
= ts
.check_start
;
721 && strncmp(current
->name
, ts
.check_name
, len
) == 0
722 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
725 current
= current
->next
;
730 /* get the stuff in the scan order */
731 ts
.add_queue
= g_list_reverse(ts
.add_queue
);
732 the_queue
= ts
.add_queue
;
734 g_free(ts
.check_name
);
735 ts
.check_name
= NULL
;
737 for (l
= the_queue
; l
; l
= l
->next
) {
741 g_list_free(the_queue
);
745 process_special_dirs(GList
** special_dirs
, char *file
)
748 char *buffer
= g_malloc(4096);
751 GetPrivateProfileString("Special dirs", "list",
752 "", buffer
, 4096, file
);
754 while ((token
= strtok(s
, ",")) != NULL
) {
755 *special_dirs
= g_list_prepend(*special_dirs
, g_strdup(token
));
762 should_skip_directory(char *dir
)
764 static GList
*special_dirs
;
771 process_special_dirs(&special_dirs
, profile_name
);
772 process_special_dirs(&special_dirs
, global_profile_name
);
775 for (l
= special_dirs
; l
; l
= l
->next
) {
776 if (strncmp(dir
, l
->data
, strlen(l
->data
)) == 0)
783 tree_store_rescan(char *dir
)
790 if (should_skip_directory(dir
)) {
791 entry
= tree_store_add_entry(dir
);
797 entry
= tree_store_start_check(dir
);
802 dirp
= mc_opendir(dir
);
804 for (dp
= mc_readdir(dirp
); dp
; dp
= mc_readdir(dirp
)) {
807 if (dp
->d_name
[0] == '.') {
808 if (dp
->d_name
[1] == 0
809 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
813 full_name
= concat_dir_and_file(dir
, dp
->d_name
);
814 if (mc_lstat(full_name
, &buf
) != -1) {
815 if (S_ISDIR(buf
.st_mode
))
816 tree_store_mark_checked(dp
->d_name
);
822 tree_store_end_check();