3 Contains a storage of the file system tree representation
5 This module has been converted to be a widget.
7 The program load and saves the tree each time the tree widget is
8 created and destroyed. This is required for the future vfs layer,
9 it will be possible to have tree views over virtual file systems.
11 Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2009,
13 The Free Software Foundation, Inc.
16 Janne Kukonlehto, 1994, 1996
18 Miguel de Icaza, 1996, 1999
20 This file is part of the Midnight Commander.
22 The Midnight Commander is free software: you can redistribute it
23 and/or modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation, either version 3 of the License,
25 or (at your option) any later version.
27 The Midnight Commander is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
32 You should have received a copy of the GNU General Public License
33 along with this program. If not, see <http://www.gnu.org/licenses/>.
37 * \brief Source: tree store
39 * Contains a storage of the file system tree representation.
48 #include <sys/types.h>
52 #include "lib/global.h"
53 #include "lib/mcconfig.h"
54 #include "lib/vfs/vfs.h"
55 #include "lib/fileloc.h"
59 #include "src/setup.h"
61 #include "treestore.h"
63 /*** global variables ****************************************************************************/
65 /*** file scope macro definitions ****************************************************************/
67 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
69 /*** file scope type declarations ****************************************************************/
71 /*** file scope variables ************************************************************************/
73 static struct TreeStore ts
;
75 static hook_t
*remove_entry_hooks
;
77 /*** file scope functions ************************************************************************/
78 /* --------------------------------------------------------------------------------------------- */
80 static tree_entry
*tree_store_add_entry (const char *name
);
82 /* --------------------------------------------------------------------------------------------- */
85 tree_store_dirty (int state
)
90 /* --------------------------------------------------------------------------------------------- */
91 /** Returns the number of common bytes in the strings. */
94 str_common (const char *s1
, const char *s2
)
98 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
103 /* --------------------------------------------------------------------------------------------- */
104 /* The directory names are arranged in a single linked list in the same
105 order as they are displayed. When the tree is displayed the expected
116 i.e. the required collating sequence when comparing two directory names is
117 '\0' < PATH_SEP < all-other-characters-in-encoding-order
119 Since strcmp doesn't fulfil this requirement we use pathcmp when
120 inserting directory names into the list. The meaning of the return value
121 of pathcmp and strcmp are the same (an integer less than, equal to, or
122 greater than zero if p1 is found to be less than, to match, or be greater
127 pathcmp (const char *p1
, const char *p2
)
129 for (; *p1
== *p2
; p1
++, p2
++)
144 /* --------------------------------------------------------------------------------------------- */
147 decode (char *buffer
)
149 char *res
= g_strdup (buffer
);
152 for (p
= q
= res
; *p
; p
++, q
++)
183 /* --------------------------------------------------------------------------------------------- */
184 /** Loads the tree store from the specified filename */
187 tree_store_load_from (char *name
)
190 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
195 g_return_val_if_fail (name
!= NULL
, FALSE
);
200 file
= fopen (name
, "r");
204 if (fgets (buffer
, sizeof (buffer
), file
) != NULL
)
206 if (strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0)
224 /* File open -> read contents */
226 while (fgets (buffer
, MC_MAXPATHLEN
, file
))
232 /* Skip invalid records */
233 if ((buffer
[0] != '0' && buffer
[0] != '1'))
236 if (buffer
[1] != ':')
239 scanned
= buffer
[0] == '1';
241 lc_name
= decode (buffer
+ 2);
242 if (lc_name
[0] != PATH_SEP
)
244 /* Clear-text decompression */
245 char *s
= strtok (lc_name
, " ");
250 different
= strtok (NULL
, "");
253 vfs_path_t
*vpath
= vfs_path_from_str (oldname
);
254 strcpy (oldname
+ common
, different
);
255 if (vfs_file_is_local (vpath
))
257 e
= tree_store_add_entry (oldname
);
258 e
->scanned
= scanned
;
260 vfs_path_free (vpath
);
266 vfs_path_t
*vpath
= vfs_path_from_str (lc_name
);
267 if (vfs_file_is_local (vpath
))
269 e
= tree_store_add_entry (lc_name
);
270 e
->scanned
= scanned
;
272 vfs_path_free (vpath
);
273 strcpy (oldname
, lc_name
);
280 /* Nothing loaded, we add some standard directories */
283 tree_store_add_entry (PATH_SEP_STR
);
284 tree_store_rescan (PATH_SEP_STR
);
291 /* --------------------------------------------------------------------------------------------- */
294 encode (const char *string
)
301 for (special_chars
= 0, p
= string
; *p
; p
++)
303 if (*p
== '\n' || *p
== '\\')
307 res
= g_malloc (p
- string
+ special_chars
+ 1);
308 for (p
= string
, q
= res
; *p
; p
++, q
++)
310 if (*p
!= '\n' && *p
!= '\\')
333 /* --------------------------------------------------------------------------------------------- */
334 /** Saves the tree to the specified filename */
337 tree_store_save_to (char *name
)
342 file
= fopen (name
, "w");
346 fprintf (file
, "%s\n", TREE_SIGNATURE
);
348 current
= ts
.tree_first
;
352 vfs_path_t
*vpath
= vfs_path_from_str (current
->name
);
354 if (vfs_file_is_local (vpath
))
356 /* Clear-text compression */
357 if (current
->prev
&& (common
= str_common (current
->prev
->name
, current
->name
)) > 2)
359 char *encoded
= encode (current
->name
+ common
);
361 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
, common
, encoded
);
366 char *encoded
= encode (current
->name
);
368 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
374 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"),
375 name
, unix_error_string (errno
));
376 vfs_path_free (vpath
);
380 vfs_path_free (vpath
);
381 current
= current
->next
;
383 tree_store_dirty (FALSE
);
389 /* --------------------------------------------------------------------------------------------- */
392 tree_store_add_entry (const char *name
)
395 tree_entry
*current
= ts
.tree_first
;
396 tree_entry
*old
= NULL
;
401 if (ts
.tree_last
&& ts
.tree_last
->next
)
404 /* Search for the correct place */
405 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);
418 /* Append to the end of the list */
435 /* Insert in to the middle of the list */
439 /* Yes, in the middle */
440 new->next
= old
->next
;
445 /* Nope, in the beginning of the list */
446 new->next
= ts
.tree_first
;
449 new->next
->prev
= new;
452 /* Calculate attributes */
453 new->name
= g_strdup (name
);
454 len
= strlen (new->name
);
456 for (i
= 0; i
< len
; i
++)
457 if (new->name
[i
] == PATH_SEP
)
460 new->subname
= new->name
+ i
+ 1;
463 submask
= new->next
->submask
;
466 submask
|= 1 << new->sublevel
;
467 submask
&= (2 << new->sublevel
) - 1;
468 new->submask
= submask
;
471 /* Correct the submasks of the previous entries */
473 while (current
&& current
->sublevel
> new->sublevel
)
475 current
->submask
|= 1 << new->sublevel
;
476 current
= current
->prev
;
479 /* The entry has now been added */
481 if (new->sublevel
> 1)
483 /* Let's check if the parent directory is in the tree */
484 char *parent
= g_strdup (new->name
);
486 for (i
= strlen (parent
) - 1; i
> 1; i
--)
488 if (parent
[i
] == PATH_SEP
)
491 tree_store_add_entry (parent
);
498 tree_store_dirty (TRUE
);
502 /* --------------------------------------------------------------------------------------------- */
505 tree_store_notify_remove (tree_entry
* entry
)
507 hook_t
*p
= remove_entry_hooks
;
508 tree_store_remove_fn r
;
512 r
= (tree_store_remove_fn
) p
->hook_fn
;
513 r (entry
, p
->hook_data
);
518 /* --------------------------------------------------------------------------------------------- */
521 remove_entry (tree_entry
* entry
)
523 tree_entry
*current
= entry
->prev
;
525 tree_entry
*ret
= NULL
;
527 tree_store_notify_remove (entry
);
529 /* Correct the submasks of the previous entries */
531 submask
= entry
->next
->submask
;
532 while (current
&& current
->sublevel
> entry
->sublevel
)
534 submask
|= 1 << current
->sublevel
;
535 submask
&= (2 << current
->sublevel
) - 1;
536 current
->submask
= submask
;
537 current
= current
->prev
;
540 /* Unlink the entry from the list */
542 entry
->prev
->next
= entry
->next
;
544 ts
.tree_first
= entry
->next
;
547 entry
->next
->prev
= entry
->prev
;
549 ts
.tree_last
= entry
->prev
;
551 /* Free the memory used by the entry */
552 g_free (entry
->name
);
558 /* --------------------------------------------------------------------------------------------- */
561 process_special_dirs (GList
** special_dirs
, char *file
)
563 gchar
**buffers
, **start_buff
;
567 cfg
= mc_config_init (file
);
571 start_buff
= buffers
= mc_config_get_string_list (cfg
, "Special dirs", "list", &buffers_len
);
574 while (*buffers
!= NULL
)
576 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
580 g_strfreev (start_buff
);
582 mc_config_deinit (cfg
);
585 /* --------------------------------------------------------------------------------------------- */
588 should_skip_directory (const char *dir
)
590 static GList
*special_dirs
= NULL
;
592 static gboolean loaded
= FALSE
;
598 process_special_dirs (&special_dirs
, profile_name
);
599 process_special_dirs (&special_dirs
, global_profile_name
);
602 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
603 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
609 /* --------------------------------------------------------------------------------------------- */
610 /*** public functions ****************************************************************************/
611 /* --------------------------------------------------------------------------------------------- */
613 /* Searches for specified directory */
615 tree_store_whereis (const char *name
)
617 tree_entry
*current
= ts
.tree_first
;
620 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
621 current
= current
->next
;
629 /* --------------------------------------------------------------------------------------------- */
632 tree_store_get (void)
637 /* --------------------------------------------------------------------------------------------- */
639 * \fn int tree_store_load(void)
640 * \brief Loads the tree from the default location
641 * \return 1 if success (true), 0 otherwise (false)
645 tree_store_load (void)
650 name
= g_build_filename (mc_config_get_cache_path (), MC_TREESTORE_FILE
, NULL
);
651 retval
= tree_store_load_from (name
);
657 /* --------------------------------------------------------------------------------------------- */
659 * \fn int tree_store_save(void)
660 * \brief Saves the tree to the default file in an atomic fashion
661 * \return 0 if success, errno on error
665 tree_store_save (void)
670 name
= g_build_filename (mc_config_get_cache_path (), MC_TREESTORE_FILE
, NULL
);
671 mc_util_make_backup_if_possible (name
, ".tmp");
673 retval
= tree_store_save_to (name
);
676 mc_util_restore_from_backup_if_possible (name
, ".tmp");
681 mc_util_unlink_backup_if_possible (name
, ".tmp");
686 /* --------------------------------------------------------------------------------------------- */
689 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
691 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
694 /* --------------------------------------------------------------------------------------------- */
697 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
699 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
703 /* --------------------------------------------------------------------------------------------- */
706 tree_store_remove_entry (const char *name
)
708 tree_entry
*current
, *base
, *old
;
711 g_return_if_fail (name
!= NULL
);
713 /* Miguel Ugly hack */
714 if (name
[0] == PATH_SEP
&& name
[1] == 0)
716 /* Miguel Ugly hack end */
718 base
= tree_store_whereis (name
);
720 return; /* Doesn't exist */
722 len
= strlen (base
->name
);
723 current
= base
->next
;
725 && strncmp (current
->name
, base
->name
, len
) == 0
726 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
))
729 current
= current
->next
;
733 tree_store_dirty (TRUE
);
738 /* --------------------------------------------------------------------------------------------- */
739 /** This subdirectory exists -> clear deletion mark */
742 tree_store_mark_checked (const char *subname
)
745 tree_entry
*current
, *base
;
750 if (ts
.check_name
== NULL
)
753 /* Calculate the full name of the subdirectory */
754 if (subname
[0] == '.' && (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
756 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
757 name
= g_strconcat (PATH_SEP_STR
, subname
, (char *) NULL
);
759 name
= concat_dir_and_file (ts
.check_name
, subname
);
761 /* Search for the subdirectory */
762 current
= ts
.check_start
;
763 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
764 current
= current
->next
;
768 /* Doesn't exist -> add it */
769 current
= tree_store_add_entry (name
);
770 ts
.add_queue
= g_list_prepend (ts
.add_queue
, g_strdup (name
));
774 /* Clear the deletion mark from the subdirectory and its children */
778 len
= strlen (base
->name
);
780 current
= base
->next
;
782 && strncmp (current
->name
, base
->name
, len
) == 0
783 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
786 current
= current
->next
;
791 /* --------------------------------------------------------------------------------------------- */
792 /** Mark the subdirectories of the current directory for delete */
795 tree_store_start_check (const char *path
)
797 tree_entry
*current
, *retval
;
803 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
804 ts
.check_start
= NULL
;
806 /* Search for the start of subdirectories */
807 current
= tree_store_whereis (path
);
812 if (mc_stat (path
, &s
) == -1)
815 if (!S_ISDIR (s
.st_mode
))
818 current
= tree_store_add_entry (path
);
819 ts
.check_name
= g_strdup (path
);
824 ts
.check_name
= g_strdup (path
);
828 /* Mark old subdirectories for delete */
829 ts
.check_start
= current
->next
;
830 len
= strlen (ts
.check_name
);
832 current
= ts
.check_start
;
834 && strncmp (current
->name
, ts
.check_name
, len
) == 0
835 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
838 current
= current
->next
;
844 /* --------------------------------------------------------------------------------------------- */
845 /** Delete subdirectories which still have the deletion mark */
848 tree_store_end_check (void)
850 tree_entry
*current
, *old
;
857 g_return_if_fail (ts
.check_name
!= NULL
);
859 /* Check delete marks and delete if found */
860 len
= strlen (ts
.check_name
);
862 current
= ts
.check_start
;
864 && strncmp (current
->name
, ts
.check_name
, len
) == 0
865 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
868 current
= current
->next
;
873 /* get the stuff in the scan order */
874 ts
.add_queue
= g_list_reverse (ts
.add_queue
);
875 the_queue
= ts
.add_queue
;
877 g_free (ts
.check_name
);
878 ts
.check_name
= NULL
;
880 g_list_foreach (the_queue
, (GFunc
) g_free
, NULL
);
881 g_list_free (the_queue
);
884 /* --------------------------------------------------------------------------------------------- */
887 tree_store_rescan (const char *dir
)
894 if (should_skip_directory (dir
))
896 entry
= tree_store_add_entry (dir
);
902 entry
= tree_store_start_check (dir
);
907 dirp
= mc_opendir (dir
);
910 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
914 if (dp
->d_name
[0] == '.')
916 if (dp
->d_name
[1] == 0 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
920 full_name
= concat_dir_and_file (dir
, dp
->d_name
);
921 if (mc_lstat (full_name
, &buf
) != -1)
923 if (S_ISDIR (buf
.st_mode
))
924 tree_store_mark_checked (dp
->d_name
);
930 tree_store_end_check ();
936 /* --------------------------------------------------------------------------------------------- */