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.
41 #include <sys/types.h>
46 #include "treestore.h"
50 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
52 static struct TreeStore ts
;
54 static tree_entry
*tree_store_add_entry(const char *name
);
57 tree_store_dirty(int state
)
62 /* Returns the number of common bytes in the strings. */
64 str_common(const char *s1
, const char *s2
)
68 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
73 /* The directory names are arranged in a single linked list in the same
74 order as they are displayed. When the tree is displayed the expected
85 i.e. the required collating sequence when comparing two directory names is
86 '\0' < PATH_SEP < all-other-characters-in-encoding-order
88 Since strcmp doesn't fulfil this requirement we use pathcmp when
89 inserting directory names into the list. The meaning of the return value
90 of pathcmp and strcmp are the same (an integer less than, equal to, or
91 greater than zero if p1 is found to be less than, to match, or be greater
95 pathcmp(const char *p1
, const char *p2
)
97 for (; *p1
== *p2
; p1
++, p2
++)
112 /* Searches for specified directory */
114 tree_store_whereis(const char *name
)
116 tree_entry
*current
= ts
.tree_first
;
119 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
120 current
= current
->next
;
137 char *res
= g_strdup(buffer
);
140 for (p
= q
= res
; *p
; p
++, q
++) {
167 /* Loads the tree store from the specified filename */
169 tree_store_load_from(char *name
)
172 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
177 g_return_val_if_fail(name
!= NULL
, FALSE
);
182 file
= fopen(name
, "r");
185 fgets(buffer
, sizeof(buffer
), file
);
187 if (strncmp(buffer
, TREE_SIGNATURE
, strlen(TREE_SIGNATURE
)) != 0) {
198 /* File open -> read contents */
200 while (fgets(buffer
, MC_MAXPATHLEN
, file
)) {
205 /* Skip invalid records */
206 if ((buffer
[0] != '0' && buffer
[0] != '1'))
209 if (buffer
[1] != ':')
212 scanned
= buffer
[0] == '1';
214 name
= decode(buffer
+ 2);
217 if (name
[0] != PATH_SEP
) {
218 /* Clear-text decompression */
219 char *s
= strtok(name
, " ");
223 different
= strtok(NULL
, "");
225 strcpy(oldname
+ common
, different
);
226 if (vfs_file_is_local(oldname
)) {
227 e
= tree_store_add_entry(oldname
);
228 e
->scanned
= scanned
;
233 if (vfs_file_is_local(name
)) {
234 e
= tree_store_add_entry(name
);
235 e
->scanned
= scanned
;
237 strcpy(oldname
, name
);
244 /* Nothing loaded, we add some standard directories */
245 if (!ts
.tree_first
) {
246 tree_store_add_entry(PATH_SEP_STR
);
247 tree_store_rescan(PATH_SEP_STR
);
258 * Loads the tree from the default location.
260 * Return value: TRUE if success, FALSE otherwise.
263 tree_store_load(void)
268 name
= concat_dir_and_file(home_dir
, MC_TREE
);
269 retval
= tree_store_load_from(name
);
276 encode(const char *string
)
283 for (special_chars
= 0, p
= string
; *p
; p
++) {
284 if (*p
== '\n' || *p
== '\\')
288 res
= g_malloc(p
- string
+ special_chars
+ 1);
289 for (p
= string
, q
= res
; *p
; p
++, q
++) {
290 if (*p
!= '\n' && *p
!= '\\') {
311 /* Saves the tree to the specified filename */
313 tree_store_save_to(char *name
)
318 file
= fopen(name
, "w");
322 fprintf(file
, "%s\n", TREE_SIGNATURE
);
324 current
= ts
.tree_first
;
328 if (vfs_file_is_local(current
->name
)) {
329 /* Clear-text compression */
332 str_common(current
->prev
->name
, current
->name
)) > 2) {
333 char *encoded
= encode(current
->name
+ common
);
335 i
= fprintf(file
, "%d:%d %s\n", current
->scanned
, common
,
339 char *encoded
= encode(current
->name
);
341 i
= fprintf(file
, "%d:%s\n", current
->scanned
, encoded
);
346 fprintf(stderr
, _("Cannot write to the %s file:\n%s\n"),
347 name
, unix_error_string(errno
));
351 current
= current
->next
;
353 tree_store_dirty(FALSE
);
363 * Saves the tree to the default file in an atomic fashion.
365 * Return value: 0 if success, errno on error.
368 tree_store_save(void)
374 tmp
= concat_dir_and_file(home_dir
, MC_TREE_TMP
);
375 retval
= tree_store_save_to(tmp
);
382 name
= concat_dir_and_file(home_dir
, MC_TREE
);
383 retval
= rename(tmp
, name
);
395 tree_store_add_entry(const char *name
)
398 tree_entry
*current
= ts
.tree_first
;
399 tree_entry
*old
= NULL
;
404 if (ts
.tree_last
&& ts
.tree_last
->next
)
407 /* Search for the correct place */
408 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0) {
410 current
= current
->next
;
414 return current
; /* Already in the list */
416 /* Not in the list -> add it */
417 new = g_new0(tree_entry
, 1);
419 /* Append to the end of the list */
420 if (!ts
.tree_first
) {
431 /* Insert in to the middle of the list */
434 /* Yes, in the middle */
435 new->next
= old
->next
;
438 /* Nope, in the beginning of the list */
439 new->next
= ts
.tree_first
;
442 new->next
->prev
= new;
445 /* Calculate attributes */
446 new->name
= g_strdup(name
);
447 len
= strlen(new->name
);
449 for (i
= 0; i
< len
; i
++)
450 if (new->name
[i
] == PATH_SEP
) {
452 new->subname
= new->name
+ i
+ 1;
455 submask
= new->next
->submask
;
458 submask
|= 1 << new->sublevel
;
459 submask
&= (2 << new->sublevel
) - 1;
460 new->submask
= submask
;
463 /* Correct the submasks of the previous entries */
465 while (current
&& current
->sublevel
> new->sublevel
) {
466 current
->submask
|= 1 << new->sublevel
;
467 current
= current
->prev
;
470 /* The entry has now been added */
472 if (new->sublevel
> 1) {
473 /* Let's check if the parent directory is in the tree */
474 char *parent
= g_strdup(new->name
);
477 for (i
= strlen(parent
) - 1; i
> 1; i
--) {
478 if (parent
[i
] == PATH_SEP
) {
480 tree_store_add_entry(parent
);
487 tree_store_dirty(TRUE
);
491 static Hook
*remove_entry_hooks
;
494 tree_store_add_entry_remove_hook(tree_store_remove_fn callback
, void *data
)
496 add_hook(&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
500 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback
)
502 delete_hook(&remove_entry_hooks
, (void (*)(void *)) callback
);
506 tree_store_notify_remove(tree_entry
* entry
)
508 Hook
*p
= remove_entry_hooks
;
509 tree_store_remove_fn r
;
512 r
= (tree_store_remove_fn
) p
->hook_fn
;
513 r(entry
, p
->hook_data
);
519 remove_entry(tree_entry
* entry
)
521 tree_entry
*current
= entry
->prev
;
523 tree_entry
*ret
= NULL
;
525 tree_store_notify_remove(entry
);
527 /* Correct the submasks of the previous entries */
529 submask
= entry
->next
->submask
;
530 while (current
&& current
->sublevel
> entry
->sublevel
) {
531 submask
|= 1 << current
->sublevel
;
532 submask
&= (2 << current
->sublevel
) - 1;
533 current
->submask
= submask
;
534 current
= current
->prev
;
537 /* Unlink the entry from the list */
539 entry
->prev
->next
= entry
->next
;
541 ts
.tree_first
= entry
->next
;
544 entry
->next
->prev
= entry
->prev
;
546 ts
.tree_last
= entry
->prev
;
548 /* Free the memory used by the entry */
556 tree_store_remove_entry(const char *name
)
558 tree_entry
*current
, *base
, *old
;
561 g_return_if_fail(name
!= NULL
);
563 /* Miguel Ugly hack */
564 if (name
[0] == PATH_SEP
&& name
[1] == 0)
566 /* Miguel Ugly hack end */
568 base
= tree_store_whereis(name
);
570 return; /* Doesn't exist */
572 len
= strlen(base
->name
);
573 current
= base
->next
;
575 && strncmp(current
->name
, base
->name
, len
) == 0
576 && (current
->name
[len
] == '\0'
577 || current
->name
[len
] == PATH_SEP
)) {
579 current
= current
->next
;
583 tree_store_dirty(TRUE
);
588 /* This subdirectory exists -> clear deletion mark */
590 tree_store_mark_checked(const char *subname
)
593 tree_entry
*current
, *base
;
598 if (ts
.check_name
== NULL
)
601 /* Calculate the full name of the subdirectory */
602 if (subname
[0] == '.' &&
603 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
605 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
606 name
= g_strconcat(PATH_SEP_STR
, subname
, (char *) NULL
);
608 name
= concat_dir_and_file(ts
.check_name
, subname
);
610 /* Search for the subdirectory */
611 current
= ts
.check_start
;
612 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
613 current
= current
->next
;
616 /* Doesn't exist -> add it */
617 current
= tree_store_add_entry(name
);
618 ts
.add_queue
= g_list_prepend(ts
.add_queue
, g_strdup(name
));
622 /* Clear the deletion mark from the subdirectory and its children */
625 len
= strlen(base
->name
);
627 current
= base
->next
;
629 && strncmp(current
->name
, base
->name
, len
) == 0
630 && (current
->name
[len
] == '\0'
631 || current
->name
[len
] == PATH_SEP
|| len
== 1)) {
633 current
= current
->next
;
638 /* Mark the subdirectories of the current directory for delete */
640 tree_store_start_check(const char *path
)
642 tree_entry
*current
, *retval
;
648 g_return_val_if_fail(ts
.check_name
== NULL
, NULL
);
649 ts
.check_start
= NULL
;
651 /* Search for the start of subdirectories */
652 current
= tree_store_whereis(path
);
656 if (mc_stat(path
, &s
) == -1)
659 if (!S_ISDIR(s
.st_mode
))
662 current
= tree_store_add_entry(path
);
663 ts
.check_name
= g_strdup(path
);
668 ts
.check_name
= g_strdup(path
);
672 /* Mark old subdirectories for delete */
673 ts
.check_start
= current
->next
;
674 len
= strlen(ts
.check_name
);
676 current
= ts
.check_start
;
678 && strncmp(current
->name
, ts
.check_name
, len
) == 0
679 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
682 current
= current
->next
;
688 /* Delete subdirectories which still have the deletion mark */
690 tree_store_end_check(void)
692 tree_entry
*current
, *old
;
694 GList
*the_queue
, *l
;
699 g_return_if_fail(ts
.check_name
!= NULL
);
701 /* Check delete marks and delete if found */
702 len
= strlen(ts
.check_name
);
704 current
= ts
.check_start
;
706 && strncmp(current
->name
, ts
.check_name
, len
) == 0
707 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
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
) {
726 g_list_free(the_queue
);
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(const char *dir
)
749 static GList
*special_dirs
;
756 process_special_dirs(&special_dirs
, profile_name
);
757 process_special_dirs(&special_dirs
, global_profile_name
);
760 for (l
= special_dirs
; l
; l
= l
->next
) {
761 if (strncmp(dir
, l
->data
, strlen(l
->data
)) == 0)
768 tree_store_rescan(const 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();