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 "src/setup.h"
59 #include "treestore.h"
61 /*** global variables ****************************************************************************/
63 /*** file scope macro definitions ****************************************************************/
65 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
67 /*** file scope type declarations ****************************************************************/
69 /*** file scope variables ************************************************************************/
71 static struct TreeStore ts
;
73 static hook_t
*remove_entry_hooks
;
75 /*** file scope functions ************************************************************************/
76 /* --------------------------------------------------------------------------------------------- */
78 static tree_entry
*tree_store_add_entry (const char *name
);
80 /* --------------------------------------------------------------------------------------------- */
83 tree_store_dirty (int state
)
88 /* --------------------------------------------------------------------------------------------- */
89 /** Returns the number of common bytes in the strings. */
92 str_common (const char *s1
, const char *s2
)
96 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
101 /* --------------------------------------------------------------------------------------------- */
102 /* The directory names are arranged in a single linked list in the same
103 order as they are displayed. When the tree is displayed the expected
114 i.e. the required collating sequence when comparing two directory names is
115 '\0' < PATH_SEP < all-other-characters-in-encoding-order
117 Since strcmp doesn't fulfil this requirement we use pathcmp when
118 inserting directory names into the list. The meaning of the return value
119 of pathcmp and strcmp are the same (an integer less than, equal to, or
120 greater than zero if p1 is found to be less than, to match, or be greater
125 pathcmp (const char *p1
, const char *p2
)
127 for (; *p1
== *p2
; p1
++, p2
++)
142 /* --------------------------------------------------------------------------------------------- */
145 decode (char *buffer
)
147 char *res
= g_strdup (buffer
);
150 for (p
= q
= res
; *p
; p
++, q
++)
181 /* --------------------------------------------------------------------------------------------- */
182 /** Loads the tree store from the specified filename */
185 tree_store_load_from (char *name
)
188 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
193 g_return_val_if_fail (name
!= NULL
, FALSE
);
198 file
= fopen (name
, "r");
202 if (fgets (buffer
, sizeof (buffer
), file
) != NULL
)
204 if (strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0)
222 /* File open -> read contents */
224 while (fgets (buffer
, MC_MAXPATHLEN
, file
))
230 /* Skip invalid records */
231 if ((buffer
[0] != '0' && buffer
[0] != '1'))
234 if (buffer
[1] != ':')
237 scanned
= buffer
[0] == '1';
239 lc_name
= decode (buffer
+ 2);
241 len
= strlen (lc_name
);
242 if (lc_name
[0] != PATH_SEP
)
244 /* Clear-text decompression */
245 char *s
= strtok (lc_name
, " ");
250 different
= strtok (NULL
, "");
253 strcpy (oldname
+ common
, different
);
254 if (vfs_file_is_local (oldname
))
256 e
= tree_store_add_entry (oldname
);
257 e
->scanned
= scanned
;
264 if (vfs_file_is_local (lc_name
))
266 e
= tree_store_add_entry (lc_name
);
267 e
->scanned
= scanned
;
269 strcpy (oldname
, lc_name
);
276 /* Nothing loaded, we add some standard directories */
279 tree_store_add_entry (PATH_SEP_STR
);
280 tree_store_rescan (PATH_SEP_STR
);
287 /* --------------------------------------------------------------------------------------------- */
290 encode (const char *string
)
297 for (special_chars
= 0, p
= string
; *p
; p
++)
299 if (*p
== '\n' || *p
== '\\')
303 res
= g_malloc (p
- string
+ special_chars
+ 1);
304 for (p
= string
, q
= res
; *p
; p
++, q
++)
306 if (*p
!= '\n' && *p
!= '\\')
329 /* --------------------------------------------------------------------------------------------- */
330 /** Saves the tree to the specified filename */
333 tree_store_save_to (char *name
)
338 file
= fopen (name
, "w");
342 fprintf (file
, "%s\n", TREE_SIGNATURE
);
344 current
= ts
.tree_first
;
349 if (vfs_file_is_local (current
->name
))
351 /* Clear-text compression */
352 if (current
->prev
&& (common
= str_common (current
->prev
->name
, current
->name
)) > 2)
354 char *encoded
= encode (current
->name
+ common
);
356 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
, common
, encoded
);
361 char *encoded
= encode (current
->name
);
363 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
369 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"),
370 name
, unix_error_string (errno
));
374 current
= current
->next
;
376 tree_store_dirty (FALSE
);
382 /* --------------------------------------------------------------------------------------------- */
385 tree_store_add_entry (const char *name
)
388 tree_entry
*current
= ts
.tree_first
;
389 tree_entry
*old
= NULL
;
394 if (ts
.tree_last
&& ts
.tree_last
->next
)
397 /* Search for the correct place */
398 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
401 current
= current
->next
;
405 return current
; /* Already in the list */
407 /* Not in the list -> add it */
408 new = g_new0 (tree_entry
, 1);
411 /* Append to the end of the list */
428 /* Insert in to the middle of the list */
432 /* Yes, in the middle */
433 new->next
= old
->next
;
438 /* Nope, in the beginning of the list */
439 new->next
= ts
.tree_first
;
442 new->next
->prev
= new;
445 /* Calculate attributes */
446 new->name
= g_strdup (name
);
447 len
= strlen (new->name
);
449 for (i
= 0; i
< len
; i
++)
450 if (new->name
[i
] == PATH_SEP
)
453 new->subname
= new->name
+ i
+ 1;
456 submask
= new->next
->submask
;
459 submask
|= 1 << new->sublevel
;
460 submask
&= (2 << new->sublevel
) - 1;
461 new->submask
= submask
;
464 /* Correct the submasks of the previous entries */
466 while (current
&& current
->sublevel
> new->sublevel
)
468 current
->submask
|= 1 << new->sublevel
;
469 current
= current
->prev
;
472 /* The entry has now been added */
474 if (new->sublevel
> 1)
476 /* Let's check if the parent directory is in the tree */
477 char *parent
= g_strdup (new->name
);
479 for (i
= strlen (parent
) - 1; i
> 1; i
--)
481 if (parent
[i
] == PATH_SEP
)
484 tree_store_add_entry (parent
);
491 tree_store_dirty (TRUE
);
495 /* --------------------------------------------------------------------------------------------- */
498 tree_store_notify_remove (tree_entry
* entry
)
500 hook_t
*p
= remove_entry_hooks
;
501 tree_store_remove_fn r
;
505 r
= (tree_store_remove_fn
) p
->hook_fn
;
506 r (entry
, p
->hook_data
);
511 /* --------------------------------------------------------------------------------------------- */
514 remove_entry (tree_entry
* entry
)
516 tree_entry
*current
= entry
->prev
;
518 tree_entry
*ret
= NULL
;
520 tree_store_notify_remove (entry
);
522 /* Correct the submasks of the previous entries */
524 submask
= entry
->next
->submask
;
525 while (current
&& current
->sublevel
> entry
->sublevel
)
527 submask
|= 1 << current
->sublevel
;
528 submask
&= (2 << current
->sublevel
) - 1;
529 current
->submask
= submask
;
530 current
= current
->prev
;
533 /* Unlink the entry from the list */
535 entry
->prev
->next
= entry
->next
;
537 ts
.tree_first
= entry
->next
;
540 entry
->next
->prev
= entry
->prev
;
542 ts
.tree_last
= entry
->prev
;
544 /* Free the memory used by the entry */
545 g_free (entry
->name
);
551 /* --------------------------------------------------------------------------------------------- */
554 process_special_dirs (GList
** special_dirs
, char *file
)
556 gchar
**buffers
, **start_buff
;
560 cfg
= mc_config_init (file
);
564 start_buff
= buffers
= mc_config_get_string_list (cfg
, "Special dirs", "list", &buffers_len
);
567 while (*buffers
!= NULL
)
569 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
573 g_strfreev (start_buff
);
575 mc_config_deinit (cfg
);
578 /* --------------------------------------------------------------------------------------------- */
581 should_skip_directory (const char *dir
)
583 static GList
*special_dirs
= NULL
;
585 static gboolean loaded
= FALSE
;
591 process_special_dirs (&special_dirs
, profile_name
);
592 process_special_dirs (&special_dirs
, global_profile_name
);
595 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
596 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
602 /* --------------------------------------------------------------------------------------------- */
603 /*** public functions ****************************************************************************/
604 /* --------------------------------------------------------------------------------------------- */
606 /* Searches for specified directory */
608 tree_store_whereis (const char *name
)
610 tree_entry
*current
= ts
.tree_first
;
613 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
614 current
= current
->next
;
622 /* --------------------------------------------------------------------------------------------- */
625 tree_store_get (void)
630 /* --------------------------------------------------------------------------------------------- */
632 * \fn int tree_store_load(void)
633 * \brief Loads the tree from the default location
634 * \return 1 if success (true), 0 otherwise (false)
638 tree_store_load (void)
643 name
= g_build_filename (mc_config_get_cache_path (), MC_TREESTORE_FILE
, NULL
);
644 retval
= tree_store_load_from (name
);
650 /* --------------------------------------------------------------------------------------------- */
652 * \fn int tree_store_save(void)
653 * \brief Saves the tree to the default file in an atomic fashion
654 * \return 0 if success, errno on error
658 tree_store_save (void)
663 name
= g_build_filename (mc_config_get_cache_path (), MC_TREESTORE_FILE
, NULL
);
664 mc_util_make_backup_if_possible (name
, ".tmp");
666 retval
= tree_store_save_to (name
);
669 mc_util_restore_from_backup_if_possible (name
, ".tmp");
674 mc_util_unlink_backup_if_possible (name
, ".tmp");
679 /* --------------------------------------------------------------------------------------------- */
682 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
684 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
687 /* --------------------------------------------------------------------------------------------- */
690 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
692 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
696 /* --------------------------------------------------------------------------------------------- */
699 tree_store_remove_entry (const char *name
)
701 tree_entry
*current
, *base
, *old
;
704 g_return_if_fail (name
!= NULL
);
706 /* Miguel Ugly hack */
707 if (name
[0] == PATH_SEP
&& name
[1] == 0)
709 /* Miguel Ugly hack end */
711 base
= tree_store_whereis (name
);
713 return; /* Doesn't exist */
715 len
= strlen (base
->name
);
716 current
= base
->next
;
718 && strncmp (current
->name
, base
->name
, len
) == 0
719 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
))
722 current
= current
->next
;
726 tree_store_dirty (TRUE
);
731 /* --------------------------------------------------------------------------------------------- */
732 /** This subdirectory exists -> clear deletion mark */
735 tree_store_mark_checked (const char *subname
)
738 tree_entry
*current
, *base
;
743 if (ts
.check_name
== NULL
)
746 /* Calculate the full name of the subdirectory */
747 if (subname
[0] == '.' && (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
749 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
750 name
= g_strconcat (PATH_SEP_STR
, subname
, (char *) NULL
);
752 name
= concat_dir_and_file (ts
.check_name
, subname
);
754 /* Search for the subdirectory */
755 current
= ts
.check_start
;
756 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
757 current
= current
->next
;
761 /* Doesn't exist -> add it */
762 current
= tree_store_add_entry (name
);
763 ts
.add_queue
= g_list_prepend (ts
.add_queue
, g_strdup (name
));
767 /* Clear the deletion mark from the subdirectory and its children */
771 len
= strlen (base
->name
);
773 current
= base
->next
;
775 && strncmp (current
->name
, base
->name
, len
) == 0
776 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
779 current
= current
->next
;
784 /* --------------------------------------------------------------------------------------------- */
785 /** Mark the subdirectories of the current directory for delete */
788 tree_store_start_check (const char *path
)
790 tree_entry
*current
, *retval
;
796 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
797 ts
.check_start
= NULL
;
799 /* Search for the start of subdirectories */
800 current
= tree_store_whereis (path
);
805 if (mc_stat (path
, &s
) == -1)
808 if (!S_ISDIR (s
.st_mode
))
811 current
= tree_store_add_entry (path
);
812 ts
.check_name
= g_strdup (path
);
817 ts
.check_name
= g_strdup (path
);
821 /* Mark old subdirectories for delete */
822 ts
.check_start
= current
->next
;
823 len
= strlen (ts
.check_name
);
825 current
= ts
.check_start
;
827 && strncmp (current
->name
, ts
.check_name
, len
) == 0
828 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
831 current
= current
->next
;
837 /* --------------------------------------------------------------------------------------------- */
838 /** Delete subdirectories which still have the deletion mark */
841 tree_store_end_check (void)
843 tree_entry
*current
, *old
;
850 g_return_if_fail (ts
.check_name
!= NULL
);
852 /* Check delete marks and delete if found */
853 len
= strlen (ts
.check_name
);
855 current
= ts
.check_start
;
857 && strncmp (current
->name
, ts
.check_name
, len
) == 0
858 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
861 current
= current
->next
;
866 /* get the stuff in the scan order */
867 ts
.add_queue
= g_list_reverse (ts
.add_queue
);
868 the_queue
= ts
.add_queue
;
870 g_free (ts
.check_name
);
871 ts
.check_name
= NULL
;
873 g_list_foreach (the_queue
, (GFunc
) g_free
, NULL
);
874 g_list_free (the_queue
);
877 /* --------------------------------------------------------------------------------------------- */
880 tree_store_rescan (const char *dir
)
887 if (should_skip_directory (dir
))
889 entry
= tree_store_add_entry (dir
);
895 entry
= tree_store_start_check (dir
);
900 dirp
= mc_opendir (dir
);
903 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
907 if (dp
->d_name
[0] == '.')
909 if (dp
->d_name
[1] == 0 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
913 full_name
= concat_dir_and_file (dir
, dp
->d_name
);
914 if (mc_lstat (full_name
, &buf
) != -1)
916 if (S_ISDIR (buf
.st_mode
))
917 tree_store_mark_checked (dp
->d_name
);
923 tree_store_end_check ();
929 /* --------------------------------------------------------------------------------------------- */