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 fgets(buffer
, sizeof(buffer
), file
);
195 if (strncmp(buffer
, TREE_SIGNATURE
, strlen(TREE_SIGNATURE
)) != 0) {
206 /* File open -> read contents */
208 while (fgets(buffer
, MC_MAXPATHLEN
, file
)) {
213 /* Skip invalid records */
214 if ((buffer
[0] != '0' && buffer
[0] != '1'))
217 if (buffer
[1] != ':')
220 scanned
= buffer
[0] == '1';
222 lc_name
= decode(buffer
+ 2);
224 len
= strlen(lc_name
);
225 if (lc_name
[0] != PATH_SEP
) {
226 /* Clear-text decompression */
227 char *s
= strtok(lc_name
, " ");
231 different
= strtok(NULL
, "");
233 strcpy(oldname
+ common
, different
);
234 if (vfs_file_is_local(oldname
)) {
235 e
= tree_store_add_entry(oldname
);
236 e
->scanned
= scanned
;
241 if (vfs_file_is_local(lc_name
)) {
242 e
= tree_store_add_entry(lc_name
);
243 e
->scanned
= scanned
;
245 strcpy(oldname
, lc_name
);
252 /* Nothing loaded, we add some standard directories */
253 if (!ts
.tree_first
) {
254 tree_store_add_entry(PATH_SEP_STR
);
255 tree_store_rescan(PATH_SEP_STR
);
263 * \fn int tree_store_load(void)
264 * \brief Loads the tree from the default location
265 * \return 1 if success (true), 0 otherwise (false)
268 tree_store_load(void)
273 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
274 retval
= tree_store_load_from(name
);
281 encode(const char *string
)
288 for (special_chars
= 0, p
= string
; *p
; p
++) {
289 if (*p
== '\n' || *p
== '\\')
293 res
= g_malloc(p
- string
+ special_chars
+ 1);
294 for (p
= string
, q
= res
; *p
; p
++, q
++) {
295 if (*p
!= '\n' && *p
!= '\\') {
316 /* Saves the tree to the specified filename */
318 tree_store_save_to(char *name
)
323 file
= fopen(name
, "w");
327 fprintf(file
, "%s\n", TREE_SIGNATURE
);
329 current
= ts
.tree_first
;
333 if (vfs_file_is_local(current
->name
)) {
334 /* Clear-text compression */
337 str_common(current
->prev
->name
, current
->name
)) > 2) {
338 char *encoded
= encode(current
->name
+ common
);
340 i
= fprintf(file
, "%d:%d %s\n", current
->scanned
, common
,
344 char *encoded
= encode(current
->name
);
346 i
= fprintf(file
, "%d:%s\n", current
->scanned
, encoded
);
351 fprintf(stderr
, _("Cannot write to the %s file:\n%s\n"),
352 name
, unix_error_string(errno
));
356 current
= current
->next
;
358 tree_store_dirty(FALSE
);
365 * \fn int tree_store_save(void)
366 * \brief Saves the tree to the default file in an atomic fashion
367 * \return 0 if success, errno on error
370 tree_store_save(void)
375 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
376 mc_util_make_backup_if_possible (name
, ".tmp");
378 if ((retval
= tree_store_save_to(name
)) != 0) {
379 mc_util_restore_from_backup_if_possible (name
, ".tmp");
384 mc_util_unlink_backup_if_possible (name
, ".tmp");
390 tree_store_add_entry(const char *name
)
393 tree_entry
*current
= ts
.tree_first
;
394 tree_entry
*old
= NULL
;
399 if (ts
.tree_last
&& ts
.tree_last
->next
)
402 /* Search for the correct place */
403 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0) {
405 current
= current
->next
;
409 return current
; /* Already in the list */
411 /* Not in the list -> add it */
412 new = g_new0(tree_entry
, 1);
414 /* Append to the end of the list */
415 if (!ts
.tree_first
) {
426 /* Insert in to the middle of the list */
429 /* Yes, in the middle */
430 new->next
= old
->next
;
433 /* Nope, in the beginning of the list */
434 new->next
= ts
.tree_first
;
437 new->next
->prev
= new;
440 /* Calculate attributes */
441 new->name
= g_strdup(name
);
442 len
= strlen(new->name
);
444 for (i
= 0; i
< len
; i
++)
445 if (new->name
[i
] == PATH_SEP
) {
447 new->subname
= new->name
+ i
+ 1;
450 submask
= new->next
->submask
;
453 submask
|= 1 << new->sublevel
;
454 submask
&= (2 << new->sublevel
) - 1;
455 new->submask
= submask
;
458 /* Correct the submasks of the previous entries */
460 while (current
&& current
->sublevel
> new->sublevel
) {
461 current
->submask
|= 1 << new->sublevel
;
462 current
= current
->prev
;
465 /* The entry has now been added */
467 if (new->sublevel
> 1) {
468 /* Let's check if the parent directory is in the tree */
469 char *parent
= g_strdup(new->name
);
471 for (i
= strlen(parent
) - 1; i
> 1; i
--) {
472 if (parent
[i
] == PATH_SEP
) {
474 tree_store_add_entry(parent
);
481 tree_store_dirty(TRUE
);
485 static Hook
*remove_entry_hooks
;
488 tree_store_add_entry_remove_hook(tree_store_remove_fn callback
, void *data
)
490 add_hook(&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
494 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback
)
496 delete_hook(&remove_entry_hooks
, (void (*)(void *)) callback
);
500 tree_store_notify_remove(tree_entry
* entry
)
502 Hook
*p
= remove_entry_hooks
;
503 tree_store_remove_fn r
;
506 r
= (tree_store_remove_fn
) p
->hook_fn
;
507 r(entry
, p
->hook_data
);
513 remove_entry(tree_entry
* entry
)
515 tree_entry
*current
= entry
->prev
;
517 tree_entry
*ret
= NULL
;
519 tree_store_notify_remove(entry
);
521 /* Correct the submasks of the previous entries */
523 submask
= entry
->next
->submask
;
524 while (current
&& current
->sublevel
> entry
->sublevel
) {
525 submask
|= 1 << current
->sublevel
;
526 submask
&= (2 << current
->sublevel
) - 1;
527 current
->submask
= submask
;
528 current
= current
->prev
;
531 /* Unlink the entry from the list */
533 entry
->prev
->next
= entry
->next
;
535 ts
.tree_first
= entry
->next
;
538 entry
->next
->prev
= entry
->prev
;
540 ts
.tree_last
= entry
->prev
;
542 /* Free the memory used by the entry */
550 tree_store_remove_entry(const char *name
)
552 tree_entry
*current
, *base
, *old
;
555 g_return_if_fail(name
!= NULL
);
557 /* Miguel Ugly hack */
558 if (name
[0] == PATH_SEP
&& name
[1] == 0)
560 /* Miguel Ugly hack end */
562 base
= tree_store_whereis(name
);
564 return; /* Doesn't exist */
566 len
= strlen(base
->name
);
567 current
= base
->next
;
569 && strncmp(current
->name
, base
->name
, len
) == 0
570 && (current
->name
[len
] == '\0'
571 || current
->name
[len
] == PATH_SEP
)) {
573 current
= current
->next
;
577 tree_store_dirty(TRUE
);
582 /* This subdirectory exists -> clear deletion mark */
584 tree_store_mark_checked(const char *subname
)
587 tree_entry
*current
, *base
;
592 if (ts
.check_name
== NULL
)
595 /* Calculate the full name of the subdirectory */
596 if (subname
[0] == '.' &&
597 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
599 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
600 name
= g_strconcat(PATH_SEP_STR
, subname
, (char *) NULL
);
602 name
= concat_dir_and_file(ts
.check_name
, subname
);
604 /* Search for the subdirectory */
605 current
= ts
.check_start
;
606 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
607 current
= current
->next
;
610 /* Doesn't exist -> add it */
611 current
= tree_store_add_entry(name
);
612 ts
.add_queue
= g_list_prepend(ts
.add_queue
, g_strdup(name
));
616 /* Clear the deletion mark from the subdirectory and its children */
619 len
= strlen(base
->name
);
621 current
= base
->next
;
623 && strncmp(current
->name
, base
->name
, len
) == 0
624 && (current
->name
[len
] == '\0'
625 || current
->name
[len
] == PATH_SEP
|| len
== 1)) {
627 current
= current
->next
;
632 /* Mark the subdirectories of the current directory for delete */
634 tree_store_start_check(const char *path
)
636 tree_entry
*current
, *retval
;
642 g_return_val_if_fail(ts
.check_name
== NULL
, NULL
);
643 ts
.check_start
= NULL
;
645 /* Search for the start of subdirectories */
646 current
= tree_store_whereis(path
);
650 if (mc_stat(path
, &s
) == -1)
653 if (!S_ISDIR(s
.st_mode
))
656 current
= tree_store_add_entry(path
);
657 ts
.check_name
= g_strdup(path
);
662 ts
.check_name
= g_strdup(path
);
666 /* Mark old subdirectories for delete */
667 ts
.check_start
= current
->next
;
668 len
= strlen(ts
.check_name
);
670 current
= ts
.check_start
;
672 && strncmp(current
->name
, ts
.check_name
, len
) == 0
673 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
676 current
= current
->next
;
682 /* Delete subdirectories which still have the deletion mark */
684 tree_store_end_check(void)
686 tree_entry
*current
, *old
;
688 GList
*the_queue
, *l
;
693 g_return_if_fail(ts
.check_name
!= NULL
);
695 /* Check delete marks and delete if found */
696 len
= strlen(ts
.check_name
);
698 current
= ts
.check_start
;
700 && strncmp(current
->name
, ts
.check_name
, len
) == 0
701 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
704 current
= current
->next
;
709 /* get the stuff in the scan order */
710 ts
.add_queue
= g_list_reverse(ts
.add_queue
);
711 the_queue
= ts
.add_queue
;
713 g_free(ts
.check_name
);
714 ts
.check_name
= NULL
;
716 for (l
= the_queue
; l
; l
= l
->next
) {
720 g_list_free(the_queue
);
724 process_special_dirs(GList
** special_dirs
, char *file
)
726 gchar
**buffers
, **start_buff
;
730 cfg
= mc_config_init(file
);
734 start_buff
= buffers
= mc_config_get_string_list(cfg
, "Special dirs", "list", &buffers_len
);
735 if (buffers
== NULL
){
736 mc_config_deinit(cfg
);
741 *special_dirs
= g_list_prepend(*special_dirs
, g_strdup(*buffers
));
744 g_strfreev(start_buff
);
745 mc_config_deinit(cfg
);
749 should_skip_directory(const char *dir
)
751 static GList
*special_dirs
;
758 process_special_dirs(&special_dirs
, profile_name
);
759 process_special_dirs(&special_dirs
, global_profile_name
);
762 for (l
= special_dirs
; l
; l
= l
->next
) {
763 if (strncmp(dir
, l
->data
, strlen(l
->data
)) == 0)
770 tree_store_rescan(const char *dir
)
777 if (should_skip_directory(dir
)) {
778 entry
= tree_store_add_entry(dir
);
784 entry
= tree_store_start_check(dir
);
789 dirp
= mc_opendir(dir
);
791 for (dp
= mc_readdir(dirp
); dp
; dp
= mc_readdir(dirp
)) {
794 if (dp
->d_name
[0] == '.') {
795 if (dp
->d_name
[1] == 0
796 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
800 full_name
= concat_dir_and_file(dir
, dp
->d_name
);
801 if (mc_lstat(full_name
, &buf
) != -1) {
802 if (S_ISDIR(buf
.st_mode
))
803 tree_store_mark_checked(dp
->d_name
);
809 tree_store_end_check();