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 lc_name
= decode(buffer
+ 2);
222 len
= strlen(lc_name
);
223 if (lc_name
[0] != PATH_SEP
) {
224 /* Clear-text decompression */
225 char *s
= strtok(lc_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(lc_name
)) {
240 e
= tree_store_add_entry(lc_name
);
241 e
->scanned
= scanned
;
243 strcpy(oldname
, lc_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
);
469 for (i
= strlen(parent
) - 1; i
> 1; i
--) {
470 if (parent
[i
] == PATH_SEP
) {
472 tree_store_add_entry(parent
);
479 tree_store_dirty(TRUE
);
483 static Hook
*remove_entry_hooks
;
486 tree_store_add_entry_remove_hook(tree_store_remove_fn callback
, void *data
)
488 add_hook(&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
492 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback
)
494 delete_hook(&remove_entry_hooks
, (void (*)(void *)) callback
);
498 tree_store_notify_remove(tree_entry
* entry
)
500 Hook
*p
= remove_entry_hooks
;
501 tree_store_remove_fn r
;
504 r
= (tree_store_remove_fn
) p
->hook_fn
;
505 r(entry
, p
->hook_data
);
511 remove_entry(tree_entry
* entry
)
513 tree_entry
*current
= entry
->prev
;
515 tree_entry
*ret
= NULL
;
517 tree_store_notify_remove(entry
);
519 /* Correct the submasks of the previous entries */
521 submask
= entry
->next
->submask
;
522 while (current
&& current
->sublevel
> entry
->sublevel
) {
523 submask
|= 1 << current
->sublevel
;
524 submask
&= (2 << current
->sublevel
) - 1;
525 current
->submask
= submask
;
526 current
= current
->prev
;
529 /* Unlink the entry from the list */
531 entry
->prev
->next
= entry
->next
;
533 ts
.tree_first
= entry
->next
;
536 entry
->next
->prev
= entry
->prev
;
538 ts
.tree_last
= entry
->prev
;
540 /* Free the memory used by the entry */
548 tree_store_remove_entry(const char *name
)
550 tree_entry
*current
, *base
, *old
;
553 g_return_if_fail(name
!= NULL
);
555 /* Miguel Ugly hack */
556 if (name
[0] == PATH_SEP
&& name
[1] == 0)
558 /* Miguel Ugly hack end */
560 base
= tree_store_whereis(name
);
562 return; /* Doesn't exist */
564 len
= strlen(base
->name
);
565 current
= base
->next
;
567 && strncmp(current
->name
, base
->name
, len
) == 0
568 && (current
->name
[len
] == '\0'
569 || current
->name
[len
] == PATH_SEP
)) {
571 current
= current
->next
;
575 tree_store_dirty(TRUE
);
580 /* This subdirectory exists -> clear deletion mark */
582 tree_store_mark_checked(const char *subname
)
585 tree_entry
*current
, *base
;
590 if (ts
.check_name
== NULL
)
593 /* Calculate the full name of the subdirectory */
594 if (subname
[0] == '.' &&
595 (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
597 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
598 name
= g_strconcat(PATH_SEP_STR
, subname
, (char *) NULL
);
600 name
= concat_dir_and_file(ts
.check_name
, subname
);
602 /* Search for the subdirectory */
603 current
= ts
.check_start
;
604 while (current
&& (flag
= pathcmp(current
->name
, name
)) < 0)
605 current
= current
->next
;
608 /* Doesn't exist -> add it */
609 current
= tree_store_add_entry(name
);
610 ts
.add_queue
= g_list_prepend(ts
.add_queue
, g_strdup(name
));
614 /* Clear the deletion mark from the subdirectory and its children */
617 len
= strlen(base
->name
);
619 current
= base
->next
;
621 && strncmp(current
->name
, base
->name
, len
) == 0
622 && (current
->name
[len
] == '\0'
623 || current
->name
[len
] == PATH_SEP
|| len
== 1)) {
625 current
= current
->next
;
630 /* Mark the subdirectories of the current directory for delete */
632 tree_store_start_check(const char *path
)
634 tree_entry
*current
, *retval
;
640 g_return_val_if_fail(ts
.check_name
== NULL
, NULL
);
641 ts
.check_start
= NULL
;
643 /* Search for the start of subdirectories */
644 current
= tree_store_whereis(path
);
648 if (mc_stat(path
, &s
) == -1)
651 if (!S_ISDIR(s
.st_mode
))
654 current
= tree_store_add_entry(path
);
655 ts
.check_name
= g_strdup(path
);
660 ts
.check_name
= g_strdup(path
);
664 /* Mark old subdirectories for delete */
665 ts
.check_start
= current
->next
;
666 len
= strlen(ts
.check_name
);
668 current
= ts
.check_start
;
670 && strncmp(current
->name
, ts
.check_name
, len
) == 0
671 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
674 current
= current
->next
;
680 /* Delete subdirectories which still have the deletion mark */
682 tree_store_end_check(void)
684 tree_entry
*current
, *old
;
686 GList
*the_queue
, *l
;
691 g_return_if_fail(ts
.check_name
!= NULL
);
693 /* Check delete marks and delete if found */
694 len
= strlen(ts
.check_name
);
696 current
= ts
.check_start
;
698 && strncmp(current
->name
, ts
.check_name
, len
) == 0
699 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
702 current
= current
->next
;
707 /* get the stuff in the scan order */
708 ts
.add_queue
= g_list_reverse(ts
.add_queue
);
709 the_queue
= ts
.add_queue
;
711 g_free(ts
.check_name
);
712 ts
.check_name
= NULL
;
714 for (l
= the_queue
; l
; l
= l
->next
) {
718 g_list_free(the_queue
);
722 process_special_dirs(GList
** special_dirs
, char *file
)
724 gchar
**buffers
, **start_buff
;
728 cfg
= mc_config_init(file
);
732 start_buff
= buffers
= mc_config_get_string_list(cfg
, "Special dirs", "list", &buffers_len
);
733 if (buffers
== NULL
){
734 mc_config_deinit(cfg
);
739 *special_dirs
= g_list_prepend(*special_dirs
, g_strdup(*buffers
));
742 g_strfreev(start_buff
);
743 mc_config_deinit(cfg
);
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();