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"
57 #include "treestore.h"
60 /*** global variables ****************************************************************************/
62 /*** file scope macro definitions ****************************************************************/
64 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
66 /*** file scope type declarations ****************************************************************/
68 /*** file scope variables ************************************************************************/
70 static struct TreeStore ts
;
72 static hook_t
*remove_entry_hooks
;
74 /*** file scope functions ************************************************************************/
75 /* --------------------------------------------------------------------------------------------- */
77 static tree_entry
*tree_store_add_entry (const char *name
);
79 /* --------------------------------------------------------------------------------------------- */
82 tree_store_dirty (int state
)
87 /* --------------------------------------------------------------------------------------------- */
88 /** Returns the number of common bytes in the strings. */
91 str_common (const char *s1
, const char *s2
)
95 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
100 /* --------------------------------------------------------------------------------------------- */
101 /* The directory names are arranged in a single linked list in the same
102 order as they are displayed. When the tree is displayed the expected
113 i.e. the required collating sequence when comparing two directory names is
114 '\0' < PATH_SEP < all-other-characters-in-encoding-order
116 Since strcmp doesn't fulfil this requirement we use pathcmp when
117 inserting directory names into the list. The meaning of the return value
118 of pathcmp and strcmp are the same (an integer less than, equal to, or
119 greater than zero if p1 is found to be less than, to match, or be greater
124 pathcmp (const char *p1
, const char *p2
)
126 for (; *p1
== *p2
; p1
++, p2
++)
141 /* --------------------------------------------------------------------------------------------- */
144 decode (char *buffer
)
146 char *res
= g_strdup (buffer
);
149 for (p
= q
= res
; *p
; p
++, q
++)
180 /* --------------------------------------------------------------------------------------------- */
181 /** Loads the tree store from the specified filename */
184 tree_store_load_from (char *name
)
187 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
192 g_return_val_if_fail (name
!= NULL
, FALSE
);
197 file
= fopen (name
, "r");
201 if (fgets (buffer
, sizeof (buffer
), file
) != NULL
)
203 if (strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0)
221 /* File open -> read contents */
223 while (fgets (buffer
, MC_MAXPATHLEN
, file
))
229 /* Skip invalid records */
230 if ((buffer
[0] != '0' && buffer
[0] != '1'))
233 if (buffer
[1] != ':')
236 scanned
= buffer
[0] == '1';
238 lc_name
= decode (buffer
+ 2);
240 len
= strlen (lc_name
);
241 if (lc_name
[0] != PATH_SEP
)
243 /* Clear-text decompression */
244 char *s
= strtok (lc_name
, " ");
249 different
= strtok (NULL
, "");
252 strcpy (oldname
+ common
, different
);
253 if (vfs_file_is_local (oldname
))
255 e
= tree_store_add_entry (oldname
);
256 e
->scanned
= scanned
;
263 if (vfs_file_is_local (lc_name
))
265 e
= tree_store_add_entry (lc_name
);
266 e
->scanned
= scanned
;
268 strcpy (oldname
, lc_name
);
275 /* Nothing loaded, we add some standard directories */
278 tree_store_add_entry (PATH_SEP_STR
);
279 tree_store_rescan (PATH_SEP_STR
);
286 /* --------------------------------------------------------------------------------------------- */
289 encode (const char *string
)
296 for (special_chars
= 0, p
= string
; *p
; p
++)
298 if (*p
== '\n' || *p
== '\\')
302 res
= g_malloc (p
- string
+ special_chars
+ 1);
303 for (p
= string
, q
= res
; *p
; p
++, q
++)
305 if (*p
!= '\n' && *p
!= '\\')
328 /* --------------------------------------------------------------------------------------------- */
329 /** Saves the tree to the specified filename */
332 tree_store_save_to (char *name
)
337 file
= fopen (name
, "w");
341 fprintf (file
, "%s\n", TREE_SIGNATURE
);
343 current
= ts
.tree_first
;
348 if (vfs_file_is_local (current
->name
))
350 /* Clear-text compression */
351 if (current
->prev
&& (common
= str_common (current
->prev
->name
, current
->name
)) > 2)
353 char *encoded
= encode (current
->name
+ common
);
355 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
, common
, encoded
);
360 char *encoded
= encode (current
->name
);
362 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
368 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"),
369 name
, unix_error_string (errno
));
373 current
= current
->next
;
375 tree_store_dirty (FALSE
);
381 /* --------------------------------------------------------------------------------------------- */
384 tree_store_add_entry (const char *name
)
387 tree_entry
*current
= ts
.tree_first
;
388 tree_entry
*old
= NULL
;
393 if (ts
.tree_last
&& ts
.tree_last
->next
)
396 /* Search for the correct place */
397 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
400 current
= current
->next
;
404 return current
; /* Already in the list */
406 /* Not in the list -> add it */
407 new = g_new0 (tree_entry
, 1);
410 /* Append to the end of the list */
427 /* Insert in to the middle of the list */
431 /* Yes, in the middle */
432 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
)
452 new->subname
= new->name
+ i
+ 1;
455 submask
= new->next
->submask
;
458 submask
|= 1 << new->sublevel
;
459 submask
&= (2 << new->sublevel
) - 1;
460 new->submask
= submask
;
463 /* Correct the submasks of the previous entries */
465 while (current
&& current
->sublevel
> new->sublevel
)
467 current
->submask
|= 1 << new->sublevel
;
468 current
= current
->prev
;
471 /* The entry has now been added */
473 if (new->sublevel
> 1)
475 /* Let's check if the parent directory is in the tree */
476 char *parent
= g_strdup (new->name
);
478 for (i
= strlen (parent
) - 1; i
> 1; i
--)
480 if (parent
[i
] == PATH_SEP
)
483 tree_store_add_entry (parent
);
490 tree_store_dirty (TRUE
);
494 /* --------------------------------------------------------------------------------------------- */
497 tree_store_notify_remove (tree_entry
* entry
)
499 hook_t
*p
= remove_entry_hooks
;
500 tree_store_remove_fn r
;
504 r
= (tree_store_remove_fn
) p
->hook_fn
;
505 r (entry
, p
->hook_data
);
510 /* --------------------------------------------------------------------------------------------- */
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
)
526 submask
|= 1 << current
->sublevel
;
527 submask
&= (2 << current
->sublevel
) - 1;
528 current
->submask
= submask
;
529 current
= current
->prev
;
532 /* Unlink the entry from the list */
534 entry
->prev
->next
= entry
->next
;
536 ts
.tree_first
= entry
->next
;
539 entry
->next
->prev
= entry
->prev
;
541 ts
.tree_last
= entry
->prev
;
543 /* Free the memory used by the entry */
544 g_free (entry
->name
);
550 /* --------------------------------------------------------------------------------------------- */
553 process_special_dirs (GList
** special_dirs
, char *file
)
555 gchar
**buffers
, **start_buff
;
559 cfg
= mc_config_init (file
);
563 start_buff
= buffers
= mc_config_get_string_list (cfg
, "Special dirs", "list", &buffers_len
);
566 while (*buffers
!= NULL
)
568 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
572 g_strfreev (start_buff
);
574 mc_config_deinit (cfg
);
577 /* --------------------------------------------------------------------------------------------- */
580 should_skip_directory (const char *dir
)
582 static GList
*special_dirs
= NULL
;
584 static gboolean loaded
= FALSE
;
590 process_special_dirs (&special_dirs
, profile_name
);
591 process_special_dirs (&special_dirs
, global_profile_name
);
594 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
595 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
601 /* --------------------------------------------------------------------------------------------- */
602 /*** public functions ****************************************************************************/
603 /* --------------------------------------------------------------------------------------------- */
605 /* Searches for specified directory */
607 tree_store_whereis (const char *name
)
609 tree_entry
*current
= ts
.tree_first
;
612 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
613 current
= current
->next
;
621 /* --------------------------------------------------------------------------------------------- */
624 tree_store_get (void)
629 /* --------------------------------------------------------------------------------------------- */
631 * \fn int tree_store_load(void)
632 * \brief Loads the tree from the default location
633 * \return 1 if success (true), 0 otherwise (false)
637 tree_store_load (void)
642 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
643 retval
= tree_store_load_from (name
);
649 /* --------------------------------------------------------------------------------------------- */
651 * \fn int tree_store_save(void)
652 * \brief Saves the tree to the default file in an atomic fashion
653 * \return 0 if success, errno on error
657 tree_store_save (void)
662 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
663 mc_util_make_backup_if_possible (name
, ".tmp");
665 retval
= tree_store_save_to (name
);
668 mc_util_restore_from_backup_if_possible (name
, ".tmp");
673 mc_util_unlink_backup_if_possible (name
, ".tmp");
678 /* --------------------------------------------------------------------------------------------- */
681 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
683 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
686 /* --------------------------------------------------------------------------------------------- */
689 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
691 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
695 /* --------------------------------------------------------------------------------------------- */
698 tree_store_remove_entry (const char *name
)
700 tree_entry
*current
, *base
, *old
;
703 g_return_if_fail (name
!= NULL
);
705 /* Miguel Ugly hack */
706 if (name
[0] == PATH_SEP
&& name
[1] == 0)
708 /* Miguel Ugly hack end */
710 base
= tree_store_whereis (name
);
712 return; /* Doesn't exist */
714 len
= strlen (base
->name
);
715 current
= base
->next
;
717 && strncmp (current
->name
, base
->name
, len
) == 0
718 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
))
721 current
= current
->next
;
725 tree_store_dirty (TRUE
);
730 /* --------------------------------------------------------------------------------------------- */
731 /** This subdirectory exists -> clear deletion mark */
734 tree_store_mark_checked (const char *subname
)
737 tree_entry
*current
, *base
;
742 if (ts
.check_name
== NULL
)
745 /* Calculate the full name of the subdirectory */
746 if (subname
[0] == '.' && (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
748 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
749 name
= g_strconcat (PATH_SEP_STR
, subname
, (char *) NULL
);
751 name
= concat_dir_and_file (ts
.check_name
, subname
);
753 /* Search for the subdirectory */
754 current
= ts
.check_start
;
755 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
756 current
= current
->next
;
760 /* Doesn't exist -> add it */
761 current
= tree_store_add_entry (name
);
762 ts
.add_queue
= g_list_prepend (ts
.add_queue
, g_strdup (name
));
766 /* Clear the deletion mark from the subdirectory and its children */
770 len
= strlen (base
->name
);
772 current
= base
->next
;
774 && strncmp (current
->name
, base
->name
, len
) == 0
775 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
778 current
= current
->next
;
783 /* --------------------------------------------------------------------------------------------- */
784 /** Mark the subdirectories of the current directory for delete */
787 tree_store_start_check (const char *path
)
789 tree_entry
*current
, *retval
;
795 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
796 ts
.check_start
= NULL
;
798 /* Search for the start of subdirectories */
799 current
= tree_store_whereis (path
);
804 if (mc_stat (path
, &s
) == -1)
807 if (!S_ISDIR (s
.st_mode
))
810 current
= tree_store_add_entry (path
);
811 ts
.check_name
= g_strdup (path
);
816 ts
.check_name
= g_strdup (path
);
820 /* Mark old subdirectories for delete */
821 ts
.check_start
= current
->next
;
822 len
= strlen (ts
.check_name
);
824 current
= ts
.check_start
;
826 && strncmp (current
->name
, ts
.check_name
, len
) == 0
827 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
830 current
= current
->next
;
836 /* --------------------------------------------------------------------------------------------- */
837 /** Delete subdirectories which still have the deletion mark */
840 tree_store_end_check (void)
842 tree_entry
*current
, *old
;
849 g_return_if_fail (ts
.check_name
!= NULL
);
851 /* Check delete marks and delete if found */
852 len
= strlen (ts
.check_name
);
854 current
= ts
.check_start
;
856 && strncmp (current
->name
, ts
.check_name
, len
) == 0
857 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
860 current
= current
->next
;
865 /* get the stuff in the scan order */
866 ts
.add_queue
= g_list_reverse (ts
.add_queue
);
867 the_queue
= ts
.add_queue
;
869 g_free (ts
.check_name
);
870 ts
.check_name
= NULL
;
872 g_list_foreach (the_queue
, (GFunc
) g_free
, NULL
);
873 g_list_free (the_queue
);
876 /* --------------------------------------------------------------------------------------------- */
879 tree_store_rescan (const char *dir
)
886 if (should_skip_directory (dir
))
888 entry
= tree_store_add_entry (dir
);
894 entry
= tree_store_start_check (dir
);
899 dirp
= mc_opendir (dir
);
902 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
906 if (dp
->d_name
[0] == '.')
908 if (dp
->d_name
[1] == 0 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
912 full_name
= concat_dir_and_file (dir
, dp
->d_name
);
913 if (mc_lstat (full_name
, &buf
) != -1)
915 if (S_ISDIR (buf
.st_mode
))
916 tree_store_mark_checked (dp
->d_name
);
922 tree_store_end_check ();
928 /* --------------------------------------------------------------------------------------------- */