4 * Contains a storage of the file system tree representation
6 Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2009
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.
35 * \brief Source: tree store
37 * Contains a storage of the file system tree representation.
46 #include <sys/types.h>
51 #include "treestore.h"
52 #include "../src/mcconfig/mcconfig.h"
55 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
57 static struct TreeStore ts
;
59 static tree_entry
*tree_store_add_entry(const char *name
);
62 tree_store_dirty(int state
)
67 /* Returns the number of common bytes in the strings. */
69 str_common(const char *s1
, const char *s2
)
73 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
78 /* The directory names are arranged in a single linked list in the same
79 order as they are displayed. When the tree is displayed the expected
90 i.e. the required collating sequence when comparing two directory names is
91 '\0' < PATH_SEP < all-other-characters-in-encoding-order
93 Since strcmp doesn't fulfil this requirement we use pathcmp when
94 inserting directory names into the list. The meaning of the return value
95 of pathcmp and strcmp are the same (an integer less than, equal to, or
96 greater than zero if p1 is found to be less than, to match, or be greater
100 pathcmp(const char *p1
, const char *p2
)
102 for (; *p1
== *p2
; p1
++, p2
++)
117 /* Searches for specified directory */
119 tree_store_whereis(const char *name
)
121 tree_entry
*current
= ts
.tree_first
;
124 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
125 current
= current
->next
;
142 char *res
= g_strdup(buffer
);
145 for (p
= q
= res
; *p
; p
++, q
++) {
172 /* Loads the tree store from the specified filename */
174 tree_store_load_from(char *name
)
177 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
182 g_return_val_if_fail(name
!= NULL
, FALSE
);
187 file
= fopen(name
, "r");
190 fgets(buffer
, sizeof(buffer
), file
);
192 if (strncmp(buffer
, TREE_SIGNATURE
, strlen(TREE_SIGNATURE
)) != 0) {
203 /* File open -> read contents */
205 while (fgets(buffer
, MC_MAXPATHLEN
, file
)) {
210 /* Skip invalid records */
211 if ((buffer
[0] != '0' && buffer
[0] != '1'))
214 if (buffer
[1] != ':')
217 scanned
= buffer
[0] == '1';
219 name
= decode(buffer
+ 2);
222 if (name
[0] != PATH_SEP
) {
223 /* Clear-text decompression */
224 char *s
= strtok(name
, " ");
228 different
= strtok(NULL
, "");
230 strcpy(oldname
+ common
, different
);
231 if (vfs_file_is_local(oldname
)) {
232 e
= tree_store_add_entry(oldname
);
233 e
->scanned
= scanned
;
238 if (vfs_file_is_local(name
)) {
239 e
= tree_store_add_entry(name
);
240 e
->scanned
= scanned
;
242 strcpy(oldname
, name
);
249 /* Nothing loaded, we add some standard directories */
250 if (!ts
.tree_first
) {
251 tree_store_add_entry(PATH_SEP_STR
);
252 tree_store_rescan(PATH_SEP_STR
);
260 * \fn int tree_store_load(void)
261 * \brief Loads the tree from the default location
262 * \return 1 if success (true), 0 otherwise (false)
265 tree_store_load(void)
270 name
= concat_dir_and_file(home_dir
, MC_TREE
);
271 retval
= tree_store_load_from(name
);
278 encode(const char *string
)
285 for (special_chars
= 0, p
= string
; *p
; p
++) {
286 if (*p
== '\n' || *p
== '\\')
290 res
= g_malloc(p
- string
+ special_chars
+ 1);
291 for (p
= string
, q
= res
; *p
; p
++, q
++) {
292 if (*p
!= '\n' && *p
!= '\\') {
313 /* Saves the tree to the specified filename */
315 tree_store_save_to(char *name
)
320 file
= fopen(name
, "w");
324 fprintf(file
, "%s\n", TREE_SIGNATURE
);
326 current
= ts
.tree_first
;
330 if (vfs_file_is_local(current
->name
)) {
331 /* Clear-text compression */
334 str_common(current
->prev
->name
, current
->name
)) > 2) {
335 char *encoded
= encode(current
->name
+ common
);
337 i
= fprintf(file
, "%d:%d %s\n", current
->scanned
, common
,
341 char *encoded
= encode(current
->name
);
343 i
= fprintf(file
, "%d:%s\n", current
->scanned
, encoded
);
348 fprintf(stderr
, _("Cannot write to the %s file:\n%s\n"),
349 name
, unix_error_string(errno
));
353 current
= current
->next
;
355 tree_store_dirty(FALSE
);
362 * \fn int tree_store_save(void)
363 * \brief Saves the tree to the default file in an atomic fashion
364 * \return 0 if success, errno on error
367 tree_store_save(void)
373 tmp
= concat_dir_and_file(home_dir
, MC_TREE_TMP
);
374 retval
= tree_store_save_to(tmp
);
381 name
= concat_dir_and_file(home_dir
, MC_TREE
);
382 retval
= rename(tmp
, name
);
394 tree_store_add_entry(const char *name
)
397 tree_entry
*current
= ts
.tree_first
;
398 tree_entry
*old
= NULL
;
403 if (ts
.tree_last
&& ts
.tree_last
->next
)
406 /* Search for the correct place */
407 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0) {
409 current
= current
->next
;
413 return current
; /* Already in the list */
415 /* Not in the list -> add it */
416 new = g_new0(tree_entry
, 1);
418 /* Append to the end of the list */
419 if (!ts
.tree_first
) {
430 /* Insert in to the middle of the list */
433 /* Yes, in the middle */
434 new->next
= old
->next
;
437 /* Nope, in the beginning of the list */
438 new->next
= ts
.tree_first
;
441 new->next
->prev
= new;
444 /* Calculate attributes */
445 new->name
= g_strdup(name
);
446 len
= strlen(new->name
);
448 for (i
= 0; i
< len
; i
++)
449 if (new->name
[i
] == PATH_SEP
) {
451 new->subname
= new->name
+ i
+ 1;
454 submask
= new->next
->submask
;
457 submask
|= 1 << new->sublevel
;
458 submask
&= (2 << new->sublevel
) - 1;
459 new->submask
= submask
;
462 /* Correct the submasks of the previous entries */
464 while (current
&& current
->sublevel
> new->sublevel
) {
465 current
->submask
|= 1 << new->sublevel
;
466 current
= current
->prev
;
469 /* The entry has now been added */
471 if (new->sublevel
> 1) {
472 /* Let's check if the parent directory is in the tree */
473 char *parent
= g_strdup(new->name
);
476 for (i
= strlen(parent
) - 1; i
> 1; i
--) {
477 if (parent
[i
] == PATH_SEP
) {
479 tree_store_add_entry(parent
);
486 tree_store_dirty(TRUE
);
490 static Hook
*remove_entry_hooks
;
493 tree_store_add_entry_remove_hook(tree_store_remove_fn callback
, void *data
)
495 add_hook(&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
499 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback
)
501 delete_hook(&remove_entry_hooks
, (void (*)(void *)) callback
);
505 tree_store_notify_remove(tree_entry
* entry
)
507 Hook
*p
= remove_entry_hooks
;
508 tree_store_remove_fn r
;
511 r
= (tree_store_remove_fn
) p
->hook_fn
;
512 r(entry
, p
->hook_data
);
518 remove_entry(tree_entry
* entry
)
520 tree_entry
*current
= entry
->prev
;
522 tree_entry
*ret
= NULL
;
524 tree_store_notify_remove(entry
);
526 /* Correct the submasks of the previous entries */
528 submask
= entry
->next
->submask
;
529 while (current
&& current
->sublevel
> entry
->sublevel
) {
530 submask
|= 1 << current
->sublevel
;
531 submask
&= (2 << current
->sublevel
) - 1;
532 current
->submask
= submask
;
533 current
= current
->prev
;
536 /* Unlink the entry from the list */
538 entry
->prev
->next
= entry
->next
;
540 ts
.tree_first
= entry
->next
;
543 entry
->next
->prev
= entry
->prev
;
545 ts
.tree_last
= entry
->prev
;
547 /* Free the memory used by the entry */
555 tree_store_remove_entry(const char *name
)
557 tree_entry
*current
, *base
, *old
;
560 g_return_if_fail(name
!= NULL
);
562 /* Miguel Ugly hack */
563 if (name
[0] == PATH_SEP
&& name
[1] == 0)
565 /* Miguel Ugly hack end */
567 base
= tree_store_whereis(name
);
569 return; /* Doesn't exist */
571 len
= strlen(base
->name
);
572 current
= base
->next
;
574 && strncmp(current
->name
, base
->name
, len
) == 0
575 && (current
->name
[len
] == '\0'
576 || current
->name
[len
] == PATH_SEP
)) {
578 current
= current
->next
;
582 tree_store_dirty(TRUE
);
587 /* This subdirectory exists -> clear deletion mark */
589 tree_store_mark_checked(const char *subname
)
592 tree_entry
*current
, *base
;
597 if (ts
.check_name
== NULL
)
600 /* Calculate the full name of the subdirectory */
601 if (subname
[0] == '.' &&
602 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
604 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
605 name
= g_strconcat(PATH_SEP_STR
, subname
, (char *) NULL
);
607 name
= concat_dir_and_file(ts
.check_name
, subname
);
609 /* Search for the subdirectory */
610 current
= ts
.check_start
;
611 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
612 current
= current
->next
;
615 /* Doesn't exist -> add it */
616 current
= tree_store_add_entry(name
);
617 ts
.add_queue
= g_list_prepend(ts
.add_queue
, g_strdup(name
));
621 /* Clear the deletion mark from the subdirectory and its children */
624 len
= strlen(base
->name
);
626 current
= base
->next
;
628 && strncmp(current
->name
, base
->name
, len
) == 0
629 && (current
->name
[len
] == '\0'
630 || current
->name
[len
] == PATH_SEP
|| len
== 1)) {
632 current
= current
->next
;
637 /* Mark the subdirectories of the current directory for delete */
639 tree_store_start_check(const char *path
)
641 tree_entry
*current
, *retval
;
647 g_return_val_if_fail(ts
.check_name
== NULL
, NULL
);
648 ts
.check_start
= NULL
;
650 /* Search for the start of subdirectories */
651 current
= tree_store_whereis(path
);
655 if (mc_stat(path
, &s
) == -1)
658 if (!S_ISDIR(s
.st_mode
))
661 current
= tree_store_add_entry(path
);
662 ts
.check_name
= g_strdup(path
);
667 ts
.check_name
= g_strdup(path
);
671 /* Mark old subdirectories for delete */
672 ts
.check_start
= current
->next
;
673 len
= strlen(ts
.check_name
);
675 current
= ts
.check_start
;
677 && strncmp(current
->name
, ts
.check_name
, len
) == 0
678 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
681 current
= current
->next
;
687 /* Delete subdirectories which still have the deletion mark */
689 tree_store_end_check(void)
691 tree_entry
*current
, *old
;
693 GList
*the_queue
, *l
;
698 g_return_if_fail(ts
.check_name
!= NULL
);
700 /* Check delete marks and delete if found */
701 len
= strlen(ts
.check_name
);
703 current
= ts
.check_start
;
705 && strncmp(current
->name
, ts
.check_name
, len
) == 0
706 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
709 current
= current
->next
;
714 /* get the stuff in the scan order */
715 ts
.add_queue
= g_list_reverse(ts
.add_queue
);
716 the_queue
= ts
.add_queue
;
718 g_free(ts
.check_name
);
719 ts
.check_name
= NULL
;
721 for (l
= the_queue
; l
; l
= l
->next
) {
725 g_list_free(the_queue
);
729 process_special_dirs(GList
** special_dirs
, char *file
)
731 gchar
**buffers
, **start_buff
;
735 cfg
= mc_config_init(file
);
739 start_buff
= buffers
= mc_config_get_string_list(cfg
, "Special dirs", "list", &buffers_len
);
740 if (buffers
== NULL
){
741 mc_config_deinit(cfg
);
746 *special_dirs
= g_list_prepend(*special_dirs
, g_strdup(*buffers
));
749 g_strfreev(start_buff
);
750 mc_config_deinit(cfg
);
754 should_skip_directory(const char *dir
)
756 static GList
*special_dirs
;
763 process_special_dirs(&special_dirs
, profile_name
);
764 process_special_dirs(&special_dirs
, global_profile_name
);
767 for (l
= special_dirs
; l
; l
= l
->next
) {
768 if (strncmp(dir
, l
->data
, strlen(l
->data
)) == 0)
775 tree_store_rescan(const char *dir
)
782 if (should_skip_directory(dir
)) {
783 entry
= tree_store_add_entry(dir
);
789 entry
= tree_store_start_check(dir
);
794 dirp
= mc_opendir(dir
);
796 for (dp
= mc_readdir(dirp
); dp
; dp
= mc_readdir(dirp
)) {
799 if (dp
->d_name
[0] == '.') {
800 if (dp
->d_name
[1] == 0
801 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
805 full_name
= concat_dir_and_file(dir
, dp
->d_name
);
806 if (mc_lstat(full_name
, &buf
) != -1) {
807 if (S_ISDIR(buf
.st_mode
))
808 tree_store_mark_checked(dp
->d_name
);
814 tree_store_end_check();