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>
50 #include "lib/global.h"
51 #include "lib/mcconfig.h"
52 #include "lib/vfs/mc-vfs/vfs.h"
53 #include "lib/fileloc.h"
55 #include "treestore.h"
58 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
60 static struct TreeStore ts
;
62 static tree_entry
*tree_store_add_entry(const char *name
);
65 tree_store_dirty(int state
)
70 /* Returns the number of common bytes in the strings. */
72 str_common(const char *s1
, const char *s2
)
76 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
81 /* The directory names are arranged in a single linked list in the same
82 order as they are displayed. When the tree is displayed the expected
93 i.e. the required collating sequence when comparing two directory names is
94 '\0' < PATH_SEP < all-other-characters-in-encoding-order
96 Since strcmp doesn't fulfil this requirement we use pathcmp when
97 inserting directory names into the list. The meaning of the return value
98 of pathcmp and strcmp are the same (an integer less than, equal to, or
99 greater than zero if p1 is found to be less than, to match, or be greater
103 pathcmp(const char *p1
, const char *p2
)
105 for (; *p1
== *p2
; p1
++, p2
++)
120 /* Searches for specified directory */
122 tree_store_whereis(const char *name
)
124 tree_entry
*current
= ts
.tree_first
;
127 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
128 current
= current
->next
;
145 char *res
= g_strdup(buffer
);
148 for (p
= q
= res
; *p
; p
++, q
++) {
175 /* Loads the tree store from the specified filename */
177 tree_store_load_from(char *name
)
180 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
185 g_return_val_if_fail(name
!= NULL
, FALSE
);
190 file
= fopen(name
, "r");
193 if ( fgets(buffer
, sizeof(buffer
), file
) != NULL
)
195 if (strncmp(buffer
, TREE_SIGNATURE
, strlen(TREE_SIGNATURE
)) != 0) {
209 /* File open -> read contents */
211 while (fgets(buffer
, MC_MAXPATHLEN
, file
)) {
216 /* Skip invalid records */
217 if ((buffer
[0] != '0' && buffer
[0] != '1'))
220 if (buffer
[1] != ':')
223 scanned
= buffer
[0] == '1';
225 lc_name
= decode(buffer
+ 2);
227 len
= strlen(lc_name
);
228 if (lc_name
[0] != PATH_SEP
) {
229 /* Clear-text decompression */
230 char *s
= strtok(lc_name
, " ");
234 different
= strtok(NULL
, "");
236 strcpy(oldname
+ common
, different
);
237 if (vfs_file_is_local(oldname
)) {
238 e
= tree_store_add_entry(oldname
);
239 e
->scanned
= scanned
;
244 if (vfs_file_is_local(lc_name
)) {
245 e
= tree_store_add_entry(lc_name
);
246 e
->scanned
= scanned
;
248 strcpy(oldname
, lc_name
);
255 /* Nothing loaded, we add some standard directories */
256 if (!ts
.tree_first
) {
257 tree_store_add_entry(PATH_SEP_STR
);
258 tree_store_rescan(PATH_SEP_STR
);
266 * \fn int tree_store_load(void)
267 * \brief Loads the tree from the default location
268 * \return 1 if success (true), 0 otherwise (false)
271 tree_store_load(void)
276 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
277 retval
= tree_store_load_from(name
);
284 encode(const char *string
)
291 for (special_chars
= 0, p
= string
; *p
; p
++) {
292 if (*p
== '\n' || *p
== '\\')
296 res
= g_malloc(p
- string
+ special_chars
+ 1);
297 for (p
= string
, q
= res
; *p
; p
++, q
++) {
298 if (*p
!= '\n' && *p
!= '\\') {
319 /* Saves the tree to the specified filename */
321 tree_store_save_to(char *name
)
326 file
= fopen(name
, "w");
330 fprintf(file
, "%s\n", TREE_SIGNATURE
);
332 current
= ts
.tree_first
;
336 if (vfs_file_is_local(current
->name
)) {
337 /* Clear-text compression */
340 str_common(current
->prev
->name
, current
->name
)) > 2) {
341 char *encoded
= encode(current
->name
+ common
);
343 i
= fprintf(file
, "%d:%d %s\n", current
->scanned
, common
,
347 char *encoded
= encode(current
->name
);
349 i
= fprintf(file
, "%d:%s\n", current
->scanned
, encoded
);
354 fprintf(stderr
, _("Cannot write to the %s file:\n%s\n"),
355 name
, unix_error_string(errno
));
359 current
= current
->next
;
361 tree_store_dirty(FALSE
);
368 * \fn int tree_store_save(void)
369 * \brief Saves the tree to the default file in an atomic fashion
370 * \return 0 if success, errno on error
373 tree_store_save(void)
378 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
379 mc_util_make_backup_if_possible (name
, ".tmp");
381 if ((retval
= tree_store_save_to(name
)) != 0) {
382 mc_util_restore_from_backup_if_possible (name
, ".tmp");
387 mc_util_unlink_backup_if_possible (name
, ".tmp");
393 tree_store_add_entry(const char *name
)
396 tree_entry
*current
= ts
.tree_first
;
397 tree_entry
*old
= NULL
;
402 if (ts
.tree_last
&& ts
.tree_last
->next
)
405 /* Search for the correct place */
406 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0) {
408 current
= current
->next
;
412 return current
; /* Already in the list */
414 /* Not in the list -> add it */
415 new = g_new0(tree_entry
, 1);
417 /* Append to the end of the list */
418 if (!ts
.tree_first
) {
429 /* Insert in to the middle of the list */
432 /* Yes, in the middle */
433 new->next
= old
->next
;
436 /* Nope, in the beginning of the list */
437 new->next
= ts
.tree_first
;
440 new->next
->prev
= new;
443 /* Calculate attributes */
444 new->name
= g_strdup(name
);
445 len
= strlen(new->name
);
447 for (i
= 0; i
< len
; i
++)
448 if (new->name
[i
] == PATH_SEP
) {
450 new->subname
= new->name
+ i
+ 1;
453 submask
= new->next
->submask
;
456 submask
|= 1 << new->sublevel
;
457 submask
&= (2 << new->sublevel
) - 1;
458 new->submask
= submask
;
461 /* Correct the submasks of the previous entries */
463 while (current
&& current
->sublevel
> new->sublevel
) {
464 current
->submask
|= 1 << new->sublevel
;
465 current
= current
->prev
;
468 /* The entry has now been added */
470 if (new->sublevel
> 1) {
471 /* Let's check if the parent directory is in the tree */
472 char *parent
= g_strdup(new->name
);
474 for (i
= strlen(parent
) - 1; i
> 1; i
--) {
475 if (parent
[i
] == PATH_SEP
) {
477 tree_store_add_entry(parent
);
484 tree_store_dirty(TRUE
);
488 static Hook
*remove_entry_hooks
;
491 tree_store_add_entry_remove_hook(tree_store_remove_fn callback
, void *data
)
493 add_hook(&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
497 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback
)
499 delete_hook(&remove_entry_hooks
, (void (*)(void *)) callback
);
503 tree_store_notify_remove(tree_entry
* entry
)
505 Hook
*p
= remove_entry_hooks
;
506 tree_store_remove_fn r
;
509 r
= (tree_store_remove_fn
) p
->hook_fn
;
510 r(entry
, p
->hook_data
);
516 remove_entry(tree_entry
* entry
)
518 tree_entry
*current
= entry
->prev
;
520 tree_entry
*ret
= NULL
;
522 tree_store_notify_remove(entry
);
524 /* Correct the submasks of the previous entries */
526 submask
= entry
->next
->submask
;
527 while (current
&& current
->sublevel
> entry
->sublevel
) {
528 submask
|= 1 << current
->sublevel
;
529 submask
&= (2 << current
->sublevel
) - 1;
530 current
->submask
= submask
;
531 current
= current
->prev
;
534 /* Unlink the entry from the list */
536 entry
->prev
->next
= entry
->next
;
538 ts
.tree_first
= entry
->next
;
541 entry
->next
->prev
= entry
->prev
;
543 ts
.tree_last
= entry
->prev
;
545 /* Free the memory used by the entry */
553 tree_store_remove_entry(const char *name
)
555 tree_entry
*current
, *base
, *old
;
558 g_return_if_fail(name
!= NULL
);
560 /* Miguel Ugly hack */
561 if (name
[0] == PATH_SEP
&& name
[1] == 0)
563 /* Miguel Ugly hack end */
565 base
= tree_store_whereis(name
);
567 return; /* Doesn't exist */
569 len
= strlen(base
->name
);
570 current
= base
->next
;
572 && strncmp(current
->name
, base
->name
, len
) == 0
573 && (current
->name
[len
] == '\0'
574 || current
->name
[len
] == PATH_SEP
)) {
576 current
= current
->next
;
580 tree_store_dirty(TRUE
);
585 /* This subdirectory exists -> clear deletion mark */
587 tree_store_mark_checked(const char *subname
)
590 tree_entry
*current
, *base
;
595 if (ts
.check_name
== NULL
)
598 /* Calculate the full name of the subdirectory */
599 if (subname
[0] == '.' &&
600 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
602 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
603 name
= g_strconcat(PATH_SEP_STR
, subname
, (char *) NULL
);
605 name
= concat_dir_and_file(ts
.check_name
, subname
);
607 /* Search for the subdirectory */
608 current
= ts
.check_start
;
609 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
610 current
= current
->next
;
613 /* Doesn't exist -> add it */
614 current
= tree_store_add_entry(name
);
615 ts
.add_queue
= g_list_prepend(ts
.add_queue
, g_strdup(name
));
619 /* Clear the deletion mark from the subdirectory and its children */
622 len
= strlen(base
->name
);
624 current
= base
->next
;
626 && strncmp(current
->name
, base
->name
, len
) == 0
627 && (current
->name
[len
] == '\0'
628 || current
->name
[len
] == PATH_SEP
|| len
== 1)) {
630 current
= current
->next
;
635 /* Mark the subdirectories of the current directory for delete */
637 tree_store_start_check(const char *path
)
639 tree_entry
*current
, *retval
;
645 g_return_val_if_fail(ts
.check_name
== NULL
, NULL
);
646 ts
.check_start
= NULL
;
648 /* Search for the start of subdirectories */
649 current
= tree_store_whereis(path
);
653 if (mc_stat(path
, &s
) == -1)
656 if (!S_ISDIR(s
.st_mode
))
659 current
= tree_store_add_entry(path
);
660 ts
.check_name
= g_strdup(path
);
665 ts
.check_name
= g_strdup(path
);
669 /* Mark old subdirectories for delete */
670 ts
.check_start
= current
->next
;
671 len
= strlen(ts
.check_name
);
673 current
= ts
.check_start
;
675 && strncmp(current
->name
, ts
.check_name
, len
) == 0
676 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
679 current
= current
->next
;
685 /* Delete subdirectories which still have the deletion mark */
687 tree_store_end_check(void)
689 tree_entry
*current
, *old
;
691 GList
*the_queue
, *l
;
696 g_return_if_fail(ts
.check_name
!= NULL
);
698 /* Check delete marks and delete if found */
699 len
= strlen(ts
.check_name
);
701 current
= ts
.check_start
;
703 && strncmp(current
->name
, ts
.check_name
, len
) == 0
704 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
707 current
= current
->next
;
712 /* get the stuff in the scan order */
713 ts
.add_queue
= g_list_reverse(ts
.add_queue
);
714 the_queue
= ts
.add_queue
;
716 g_free(ts
.check_name
);
717 ts
.check_name
= NULL
;
719 for (l
= the_queue
; l
; l
= l
->next
) {
723 g_list_free(the_queue
);
727 process_special_dirs(GList
** special_dirs
, char *file
)
729 gchar
**buffers
, **start_buff
;
733 cfg
= mc_config_init(file
);
737 start_buff
= buffers
= mc_config_get_string_list(cfg
, "Special dirs", "list", &buffers_len
);
738 if (buffers
== NULL
){
739 mc_config_deinit(cfg
);
744 *special_dirs
= g_list_prepend(*special_dirs
, g_strdup(*buffers
));
747 g_strfreev(start_buff
);
748 mc_config_deinit(cfg
);
752 should_skip_directory(const char *dir
)
754 static GList
*special_dirs
;
761 process_special_dirs(&special_dirs
, profile_name
);
762 process_special_dirs(&special_dirs
, global_profile_name
);
765 for (l
= special_dirs
; l
; l
= l
->next
) {
766 if (strncmp(dir
, l
->data
, strlen(l
->data
)) == 0)
773 tree_store_rescan(const char *dir
)
780 if (should_skip_directory(dir
)) {
781 entry
= tree_store_add_entry(dir
);
787 entry
= tree_store_start_check(dir
);
792 dirp
= mc_opendir(dir
);
794 for (dp
= mc_readdir(dirp
); dp
; dp
= mc_readdir(dirp
)) {
797 if (dp
->d_name
[0] == '.') {
798 if (dp
->d_name
[1] == 0
799 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
803 full_name
= concat_dir_and_file(dir
, dp
->d_name
);
804 if (mc_lstat(full_name
, &buf
) != -1) {
805 if (S_ISDIR(buf
.st_mode
))
806 tree_store_mark_checked(dp
->d_name
);
812 tree_store_end_check();