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"
56 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
58 static struct TreeStore ts
;
60 static tree_entry
*tree_store_add_entry(const char *name
);
63 tree_store_dirty(int state
)
68 /* Returns the number of common bytes in the strings. */
70 str_common(const char *s1
, const char *s2
)
74 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
79 /* The directory names are arranged in a single linked list in the same
80 order as they are displayed. When the tree is displayed the expected
91 i.e. the required collating sequence when comparing two directory names is
92 '\0' < PATH_SEP < all-other-characters-in-encoding-order
94 Since strcmp doesn't fulfil this requirement we use pathcmp when
95 inserting directory names into the list. The meaning of the return value
96 of pathcmp and strcmp are the same (an integer less than, equal to, or
97 greater than zero if p1 is found to be less than, to match, or be greater
101 pathcmp(const char *p1
, const char *p2
)
103 for (; *p1
== *p2
; p1
++, p2
++)
118 /* Searches for specified directory */
120 tree_store_whereis(const char *name
)
122 tree_entry
*current
= ts
.tree_first
;
125 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
126 current
= current
->next
;
143 char *res
= g_strdup(buffer
);
146 for (p
= q
= res
; *p
; p
++, q
++) {
173 /* Loads the tree store from the specified filename */
175 tree_store_load_from(char *name
)
178 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
183 g_return_val_if_fail(name
!= NULL
, FALSE
);
188 file
= fopen(name
, "r");
191 fgets(buffer
, sizeof(buffer
), file
);
193 if (strncmp(buffer
, TREE_SIGNATURE
, strlen(TREE_SIGNATURE
)) != 0) {
204 /* File open -> read contents */
206 while (fgets(buffer
, MC_MAXPATHLEN
, file
)) {
211 /* Skip invalid records */
212 if ((buffer
[0] != '0' && buffer
[0] != '1'))
215 if (buffer
[1] != ':')
218 scanned
= buffer
[0] == '1';
220 name
= decode(buffer
+ 2);
223 if (name
[0] != PATH_SEP
) {
224 /* Clear-text decompression */
225 char *s
= strtok(name
, " ");
229 different
= strtok(NULL
, "");
231 strcpy(oldname
+ common
, different
);
232 if (vfs_file_is_local(oldname
)) {
233 e
= tree_store_add_entry(oldname
);
234 e
->scanned
= scanned
;
239 if (vfs_file_is_local(name
)) {
240 e
= tree_store_add_entry(name
);
241 e
->scanned
= scanned
;
243 strcpy(oldname
, name
);
250 /* Nothing loaded, we add some standard directories */
251 if (!ts
.tree_first
) {
252 tree_store_add_entry(PATH_SEP_STR
);
253 tree_store_rescan(PATH_SEP_STR
);
261 * \fn int tree_store_load(void)
262 * \brief Loads the tree from the default location
263 * \return 1 if success (true), 0 otherwise (false)
266 tree_store_load(void)
271 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
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
);
363 * \fn int tree_store_save(void)
364 * \brief Saves the tree to the default file in an atomic fashion
365 * \return 0 if success, errno on error
368 tree_store_save(void)
373 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
374 mc_util_make_backup_if_possible (name
, ".tmp");
376 if ((retval
= tree_store_save_to(name
)) != 0) {
377 mc_util_restore_from_backup_if_possible (name
, ".tmp");
382 mc_util_unlink_backup_if_possible (name
, ".tmp");
388 tree_store_add_entry(const char *name
)
391 tree_entry
*current
= ts
.tree_first
;
392 tree_entry
*old
= NULL
;
397 if (ts
.tree_last
&& ts
.tree_last
->next
)
400 /* Search for the correct place */
401 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0) {
403 current
= current
->next
;
407 return current
; /* Already in the list */
409 /* Not in the list -> add it */
410 new = g_new0(tree_entry
, 1);
412 /* Append to the end of the list */
413 if (!ts
.tree_first
) {
424 /* Insert in to the middle of the list */
427 /* Yes, in the middle */
428 new->next
= old
->next
;
431 /* Nope, in the beginning of the list */
432 new->next
= ts
.tree_first
;
435 new->next
->prev
= new;
438 /* Calculate attributes */
439 new->name
= g_strdup(name
);
440 len
= strlen(new->name
);
442 for (i
= 0; i
< len
; i
++)
443 if (new->name
[i
] == PATH_SEP
) {
445 new->subname
= new->name
+ i
+ 1;
448 submask
= new->next
->submask
;
451 submask
|= 1 << new->sublevel
;
452 submask
&= (2 << new->sublevel
) - 1;
453 new->submask
= submask
;
456 /* Correct the submasks of the previous entries */
458 while (current
&& current
->sublevel
> new->sublevel
) {
459 current
->submask
|= 1 << new->sublevel
;
460 current
= current
->prev
;
463 /* The entry has now been added */
465 if (new->sublevel
> 1) {
466 /* Let's check if the parent directory is in the tree */
467 char *parent
= g_strdup(new->name
);
470 for (i
= strlen(parent
) - 1; i
> 1; i
--) {
471 if (parent
[i
] == PATH_SEP
) {
473 tree_store_add_entry(parent
);
480 tree_store_dirty(TRUE
);
484 static Hook
*remove_entry_hooks
;
487 tree_store_add_entry_remove_hook(tree_store_remove_fn callback
, void *data
)
489 add_hook(&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
493 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback
)
495 delete_hook(&remove_entry_hooks
, (void (*)(void *)) callback
);
499 tree_store_notify_remove(tree_entry
* entry
)
501 Hook
*p
= remove_entry_hooks
;
502 tree_store_remove_fn r
;
505 r
= (tree_store_remove_fn
) p
->hook_fn
;
506 r(entry
, p
->hook_data
);
512 remove_entry(tree_entry
* entry
)
514 tree_entry
*current
= entry
->prev
;
516 tree_entry
*ret
= NULL
;
518 tree_store_notify_remove(entry
);
520 /* Correct the submasks of the previous entries */
522 submask
= entry
->next
->submask
;
523 while (current
&& current
->sublevel
> entry
->sublevel
) {
524 submask
|= 1 << current
->sublevel
;
525 submask
&= (2 << current
->sublevel
) - 1;
526 current
->submask
= submask
;
527 current
= current
->prev
;
530 /* Unlink the entry from the list */
532 entry
->prev
->next
= entry
->next
;
534 ts
.tree_first
= entry
->next
;
537 entry
->next
->prev
= entry
->prev
;
539 ts
.tree_last
= entry
->prev
;
541 /* Free the memory used by the entry */
549 tree_store_remove_entry(const char *name
)
551 tree_entry
*current
, *base
, *old
;
554 g_return_if_fail(name
!= NULL
);
556 /* Miguel Ugly hack */
557 if (name
[0] == PATH_SEP
&& name
[1] == 0)
559 /* Miguel Ugly hack end */
561 base
= tree_store_whereis(name
);
563 return; /* Doesn't exist */
565 len
= strlen(base
->name
);
566 current
= base
->next
;
568 && strncmp(current
->name
, base
->name
, len
) == 0
569 && (current
->name
[len
] == '\0'
570 || current
->name
[len
] == PATH_SEP
)) {
572 current
= current
->next
;
576 tree_store_dirty(TRUE
);
581 /* This subdirectory exists -> clear deletion mark */
583 tree_store_mark_checked(const char *subname
)
586 tree_entry
*current
, *base
;
591 if (ts
.check_name
== NULL
)
594 /* Calculate the full name of the subdirectory */
595 if (subname
[0] == '.' &&
596 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
598 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
599 name
= g_strconcat(PATH_SEP_STR
, subname
, (char *) NULL
);
601 name
= concat_dir_and_file(ts
.check_name
, subname
);
603 /* Search for the subdirectory */
604 current
= ts
.check_start
;
605 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
606 current
= current
->next
;
609 /* Doesn't exist -> add it */
610 current
= tree_store_add_entry(name
);
611 ts
.add_queue
= g_list_prepend(ts
.add_queue
, g_strdup(name
));
615 /* Clear the deletion mark from the subdirectory and its children */
618 len
= strlen(base
->name
);
620 current
= base
->next
;
622 && strncmp(current
->name
, base
->name
, len
) == 0
623 && (current
->name
[len
] == '\0'
624 || current
->name
[len
] == PATH_SEP
|| len
== 1)) {
626 current
= current
->next
;
631 /* Mark the subdirectories of the current directory for delete */
633 tree_store_start_check(const char *path
)
635 tree_entry
*current
, *retval
;
641 g_return_val_if_fail(ts
.check_name
== NULL
, NULL
);
642 ts
.check_start
= NULL
;
644 /* Search for the start of subdirectories */
645 current
= tree_store_whereis(path
);
649 if (mc_stat(path
, &s
) == -1)
652 if (!S_ISDIR(s
.st_mode
))
655 current
= tree_store_add_entry(path
);
656 ts
.check_name
= g_strdup(path
);
661 ts
.check_name
= g_strdup(path
);
665 /* Mark old subdirectories for delete */
666 ts
.check_start
= current
->next
;
667 len
= strlen(ts
.check_name
);
669 current
= ts
.check_start
;
671 && strncmp(current
->name
, ts
.check_name
, len
) == 0
672 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
675 current
= current
->next
;
681 /* Delete subdirectories which still have the deletion mark */
683 tree_store_end_check(void)
685 tree_entry
*current
, *old
;
687 GList
*the_queue
, *l
;
692 g_return_if_fail(ts
.check_name
!= NULL
);
694 /* Check delete marks and delete if found */
695 len
= strlen(ts
.check_name
);
697 current
= ts
.check_start
;
699 && strncmp(current
->name
, ts
.check_name
, len
) == 0
700 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
703 current
= current
->next
;
708 /* get the stuff in the scan order */
709 ts
.add_queue
= g_list_reverse(ts
.add_queue
);
710 the_queue
= ts
.add_queue
;
712 g_free(ts
.check_name
);
713 ts
.check_name
= NULL
;
715 for (l
= the_queue
; l
; l
= l
->next
) {
719 g_list_free(the_queue
);
723 process_special_dirs(GList
** special_dirs
, char *file
)
725 gchar
**buffers
, **start_buff
;
729 cfg
= mc_config_init(file
);
733 start_buff
= buffers
= mc_config_get_string_list(cfg
, "Special dirs", "list", &buffers_len
);
734 if (buffers
== NULL
){
735 mc_config_deinit(cfg
);
740 *special_dirs
= g_list_prepend(*special_dirs
, g_strdup(*buffers
));
743 g_strfreev(start_buff
);
744 mc_config_deinit(cfg
);
748 should_skip_directory(const char *dir
)
750 static GList
*special_dirs
;
757 process_special_dirs(&special_dirs
, profile_name
);
758 process_special_dirs(&special_dirs
, global_profile_name
);
761 for (l
= special_dirs
; l
; l
= l
->next
) {
762 if (strncmp(dir
, l
->data
, strlen(l
->data
)) == 0)
769 tree_store_rescan(const char *dir
)
776 if (should_skip_directory(dir
)) {
777 entry
= tree_store_add_entry(dir
);
783 entry
= tree_store_start_check(dir
);
788 dirp
= mc_opendir(dir
);
790 for (dp
= mc_readdir(dirp
); dp
; dp
= mc_readdir(dirp
)) {
793 if (dp
->d_name
[0] == '.') {
794 if (dp
->d_name
[1] == 0
795 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
799 full_name
= concat_dir_and_file(dir
, dp
->d_name
);
800 if (mc_lstat(full_name
, &buf
) != -1) {
801 if (S_ISDIR(buf
.st_mode
))
802 tree_store_mark_checked(dp
->d_name
);
808 tree_store_end_check();