4 * Contains a storage of the file system tree representation
6 Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007
7 Free Software Foundation, Inc.
9 Written: 1994, 1996 Janne Kukonlehto
11 1996, 1999 Miguel de Icaza
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 2 of the License, or
16 (at your option) any later version.
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 This module has been converted to be a widget.
29 The program load and saves the tree each time the tree widget is
30 created and destroyed. This is required for the future vfs layer,
31 it will be possible to have tree views over virtual file systems.
40 #include <sys/types.h>
44 #include <mhl/types.h>
45 #include <mhl/memory.h>
46 #include <mhl/string.h>
49 #include "treestore.h"
53 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
55 static struct TreeStore ts
;
57 static tree_entry
*tree_store_add_entry(const char *name
);
60 tree_store_dirty(int state
)
65 /* Returns the number of common bytes in the strings. */
67 str_common(const char *s1
, const char *s2
)
71 while (*s1
!= '\0' && *s2
!= '\0' && *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(const char *name
)
119 tree_entry
*current
= ts
.tree_first
;
122 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
123 current
= current
->next
;
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);
220 if (name
[0] != PATH_SEP
) {
221 /* Clear-text decompression */
222 char *s
= strtok(name
, " ");
226 different
= strtok(NULL
, "");
228 strcpy(oldname
+ common
, different
);
229 if (vfs_file_is_local(oldname
)) {
230 e
= tree_store_add_entry(oldname
);
231 e
->scanned
= scanned
;
236 if (vfs_file_is_local(name
)) {
237 e
= tree_store_add_entry(name
);
238 e
->scanned
= scanned
;
240 strcpy(oldname
, name
);
247 /* Nothing loaded, we add some standard directories */
248 if (!ts
.tree_first
) {
249 tree_store_add_entry(PATH_SEP_STR
);
250 tree_store_rescan(PATH_SEP_STR
);
261 * Loads the tree from the default location.
263 * Return value: TRUE if success, FALSE otherwise.
266 tree_store_load(void)
271 name
= mhl_str_dir_plus_file(home_dir
, MC_TREE
);
272 retval
= tree_store_load_from(name
);
279 encode(const char *string
)
286 for (special_chars
= 0, p
= string
; *p
; p
++) {
287 if (*p
== '\n' || *p
== '\\')
291 res
= g_malloc(p
- string
+ special_chars
+ 1);
292 for (p
= string
, q
= res
; *p
; p
++, q
++) {
293 if (*p
!= '\n' && *p
!= '\\') {
314 /* Saves the tree to the specified filename */
316 tree_store_save_to(char *name
)
321 file
= fopen(name
, "w");
325 fprintf(file
, "%s\n", TREE_SIGNATURE
);
327 current
= ts
.tree_first
;
331 if (vfs_file_is_local(current
->name
)) {
332 /* Clear-text compression */
335 str_common(current
->prev
->name
, current
->name
)) > 2) {
336 char *encoded
= encode(current
->name
+ common
);
338 i
= fprintf(file
, "%d:%d %s\n", current
->scanned
, common
,
342 char *encoded
= encode(current
->name
);
344 i
= fprintf(file
, "%d:%s\n", current
->scanned
, encoded
);
349 fprintf(stderr
, _("Cannot write to the %s file:\n%s\n"),
350 name
, unix_error_string(errno
));
354 current
= current
->next
;
356 tree_store_dirty(FALSE
);
366 * Saves the tree to the default file in an atomic fashion.
368 * Return value: 0 if success, errno on error.
371 tree_store_save(void)
377 tmp
= mhl_str_dir_plus_file(home_dir
, MC_TREE_TMP
);
378 retval
= tree_store_save_to(tmp
);
385 name
= mhl_str_dir_plus_file(home_dir
, MC_TREE
);
386 retval
= rename(tmp
, name
);
398 tree_store_add_entry(const char *name
)
401 tree_entry
*current
= ts
.tree_first
;
402 tree_entry
*old
= NULL
;
407 if (ts
.tree_last
&& ts
.tree_last
->next
)
410 /* Search for the correct place */
411 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0) {
413 current
= current
->next
;
417 return current
; /* Already in the list */
419 /* Not in the list -> add it */
420 new = g_new0(tree_entry
, 1);
422 /* Append to the end of the list */
423 if (!ts
.tree_first
) {
434 /* Insert in to the middle of the list */
437 /* Yes, in the middle */
438 new->next
= old
->next
;
441 /* Nope, in the beginning of the list */
442 new->next
= ts
.tree_first
;
445 new->next
->prev
= new;
448 /* Calculate attributes */
449 new->name
= g_strdup(name
);
450 len
= strlen(new->name
);
452 for (i
= 0; i
< len
; i
++)
453 if (new->name
[i
] == PATH_SEP
) {
455 new->subname
= new->name
+ i
+ 1;
458 submask
= new->next
->submask
;
461 submask
|= 1 << new->sublevel
;
462 submask
&= (2 << new->sublevel
) - 1;
463 new->submask
= submask
;
466 /* Correct the submasks of the previous entries */
468 while (current
&& current
->sublevel
> new->sublevel
) {
469 current
->submask
|= 1 << new->sublevel
;
470 current
= current
->prev
;
473 /* The entry has now been added */
475 if (new->sublevel
> 1) {
476 /* Let's check if the parent directory is in the tree */
477 char *parent
= g_strdup(new->name
);
480 for (i
= strlen(parent
) - 1; i
> 1; i
--) {
481 if (parent
[i
] == PATH_SEP
) {
483 tree_store_add_entry(parent
);
490 tree_store_dirty(TRUE
);
494 static Hook
*remove_entry_hooks
;
497 tree_store_add_entry_remove_hook(tree_store_remove_fn callback
, void *data
)
499 add_hook(&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
503 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback
)
505 delete_hook(&remove_entry_hooks
, (void (*)(void *)) callback
);
509 tree_store_notify_remove(tree_entry
* entry
)
511 Hook
*p
= remove_entry_hooks
;
512 tree_store_remove_fn r
;
515 r
= (tree_store_remove_fn
) p
->hook_fn
;
516 r(entry
, p
->hook_data
);
522 remove_entry(tree_entry
* entry
)
524 tree_entry
*current
= entry
->prev
;
526 tree_entry
*ret
= NULL
;
528 tree_store_notify_remove(entry
);
530 /* Correct the submasks of the previous entries */
532 submask
= entry
->next
->submask
;
533 while (current
&& current
->sublevel
> entry
->sublevel
) {
534 submask
|= 1 << current
->sublevel
;
535 submask
&= (2 << current
->sublevel
) - 1;
536 current
->submask
= submask
;
537 current
= current
->prev
;
540 /* Unlink the entry from the list */
542 entry
->prev
->next
= entry
->next
;
544 ts
.tree_first
= entry
->next
;
547 entry
->next
->prev
= entry
->prev
;
549 ts
.tree_last
= entry
->prev
;
551 /* Free the memory used by the entry */
559 tree_store_remove_entry(const char *name
)
561 tree_entry
*current
, *base
, *old
;
564 g_return_if_fail(name
!= NULL
);
566 /* Miguel Ugly hack */
567 if (name
[0] == PATH_SEP
&& name
[1] == 0)
569 /* Miguel Ugly hack end */
571 base
= tree_store_whereis(name
);
573 return; /* Doesn't exist */
575 len
= strlen(base
->name
);
576 current
= base
->next
;
578 && strncmp(current
->name
, base
->name
, len
) == 0
579 && (current
->name
[len
] == '\0'
580 || current
->name
[len
] == PATH_SEP
)) {
582 current
= current
->next
;
586 tree_store_dirty(TRUE
);
591 /* This subdirectory exists -> clear deletion mark */
593 tree_store_mark_checked(const char *subname
)
596 tree_entry
*current
, *base
;
601 if (ts
.check_name
== NULL
)
604 /* Calculate the full name of the subdirectory */
605 if (subname
[0] == '.' &&
606 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
608 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
609 name
= g_strconcat(PATH_SEP_STR
, subname
, (char *) NULL
);
611 name
= mhl_str_dir_plus_file(ts
.check_name
, subname
);
613 /* Search for the subdirectory */
614 current
= ts
.check_start
;
615 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
616 current
= current
->next
;
619 /* Doesn't exist -> add it */
620 current
= tree_store_add_entry(name
);
621 ts
.add_queue
= g_list_prepend(ts
.add_queue
, g_strdup(name
));
625 /* Clear the deletion mark from the subdirectory and its children */
628 len
= strlen(base
->name
);
630 current
= base
->next
;
632 && strncmp(current
->name
, base
->name
, len
) == 0
633 && (current
->name
[len
] == '\0'
634 || current
->name
[len
] == PATH_SEP
|| len
== 1)) {
636 current
= current
->next
;
641 /* Mark the subdirectories of the current directory for delete */
643 tree_store_start_check(const char *path
)
645 tree_entry
*current
, *retval
;
651 g_return_val_if_fail(ts
.check_name
== NULL
, NULL
);
652 ts
.check_start
= NULL
;
654 /* Search for the start of subdirectories */
655 current
= tree_store_whereis(path
);
659 if (mc_stat(path
, &s
) == -1)
662 if (!S_ISDIR(s
.st_mode
))
665 current
= tree_store_add_entry(path
);
666 ts
.check_name
= g_strdup(path
);
671 ts
.check_name
= g_strdup(path
);
675 /* Mark old subdirectories for delete */
676 ts
.check_start
= current
->next
;
677 len
= strlen(ts
.check_name
);
679 current
= ts
.check_start
;
681 && strncmp(current
->name
, ts
.check_name
, len
) == 0
682 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
685 current
= current
->next
;
691 /* Delete subdirectories which still have the deletion mark */
693 tree_store_end_check(void)
695 tree_entry
*current
, *old
;
697 GList
*the_queue
, *l
;
702 g_return_if_fail(ts
.check_name
!= NULL
);
704 /* Check delete marks and delete if found */
705 len
= strlen(ts
.check_name
);
707 current
= ts
.check_start
;
709 && strncmp(current
->name
, ts
.check_name
, len
) == 0
710 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
713 current
= current
->next
;
718 /* get the stuff in the scan order */
719 ts
.add_queue
= g_list_reverse(ts
.add_queue
);
720 the_queue
= ts
.add_queue
;
722 g_free(ts
.check_name
);
723 ts
.check_name
= NULL
;
725 for (l
= the_queue
; l
; l
= l
->next
) {
729 g_list_free(the_queue
);
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(const char *dir
)
752 static GList
*special_dirs
;
759 process_special_dirs(&special_dirs
, profile_name
);
760 process_special_dirs(&special_dirs
, global_profile_name
);
763 for (l
= special_dirs
; l
; l
= l
->next
) {
764 if (strncmp(dir
, l
->data
, strlen(l
->data
)) == 0)
771 tree_store_rescan(const 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
= mhl_str_dir_plus_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();