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);
240 if (lc_name
[0] != PATH_SEP
)
242 /* Clear-text decompression */
243 char *s
= strtok (lc_name
, " ");
248 different
= strtok (NULL
, "");
251 strcpy (oldname
+ common
, different
);
252 if (vfs_file_is_local (oldname
))
254 e
= tree_store_add_entry (oldname
);
255 e
->scanned
= scanned
;
262 if (vfs_file_is_local (lc_name
))
264 e
= tree_store_add_entry (lc_name
);
265 e
->scanned
= scanned
;
267 strcpy (oldname
, lc_name
);
274 /* Nothing loaded, we add some standard directories */
277 tree_store_add_entry (PATH_SEP_STR
);
278 tree_store_rescan (PATH_SEP_STR
);
285 /* --------------------------------------------------------------------------------------------- */
288 encode (const char *string
)
295 for (special_chars
= 0, p
= string
; *p
; p
++)
297 if (*p
== '\n' || *p
== '\\')
301 res
= g_malloc (p
- string
+ special_chars
+ 1);
302 for (p
= string
, q
= res
; *p
; p
++, q
++)
304 if (*p
!= '\n' && *p
!= '\\')
327 /* --------------------------------------------------------------------------------------------- */
328 /** Saves the tree to the specified filename */
331 tree_store_save_to (char *name
)
336 file
= fopen (name
, "w");
340 fprintf (file
, "%s\n", TREE_SIGNATURE
);
342 current
= ts
.tree_first
;
347 if (vfs_file_is_local (current
->name
))
349 /* Clear-text compression */
350 if (current
->prev
&& (common
= str_common (current
->prev
->name
, current
->name
)) > 2)
352 char *encoded
= encode (current
->name
+ common
);
354 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
, common
, encoded
);
359 char *encoded
= encode (current
->name
);
361 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
367 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"),
368 name
, unix_error_string (errno
));
372 current
= current
->next
;
374 tree_store_dirty (FALSE
);
380 /* --------------------------------------------------------------------------------------------- */
383 tree_store_add_entry (const char *name
)
386 tree_entry
*current
= ts
.tree_first
;
387 tree_entry
*old
= NULL
;
392 if (ts
.tree_last
&& ts
.tree_last
->next
)
395 /* Search for the correct place */
396 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
399 current
= current
->next
;
403 return current
; /* Already in the list */
405 /* Not in the list -> add it */
406 new = g_new0 (tree_entry
, 1);
409 /* Append to the end of the list */
426 /* Insert in to the middle of the list */
430 /* Yes, in the middle */
431 new->next
= old
->next
;
436 /* Nope, in the beginning of the list */
437 new->next
= ts
.tree_first
;
440 new->next
->prev
= new;
443 /* Calculate attributes */
444 new->name
= g_strdup (name
);
445 len
= strlen (new->name
);
447 for (i
= 0; i
< len
; i
++)
448 if (new->name
[i
] == PATH_SEP
)
451 new->subname
= new->name
+ i
+ 1;
454 submask
= new->next
->submask
;
457 submask
|= 1 << new->sublevel
;
458 submask
&= (2 << new->sublevel
) - 1;
459 new->submask
= submask
;
462 /* Correct the submasks of the previous entries */
464 while (current
&& current
->sublevel
> new->sublevel
)
466 current
->submask
|= 1 << new->sublevel
;
467 current
= current
->prev
;
470 /* The entry has now been added */
472 if (new->sublevel
> 1)
474 /* Let's check if the parent directory is in the tree */
475 char *parent
= g_strdup (new->name
);
477 for (i
= strlen (parent
) - 1; i
> 1; i
--)
479 if (parent
[i
] == PATH_SEP
)
482 tree_store_add_entry (parent
);
489 tree_store_dirty (TRUE
);
493 /* --------------------------------------------------------------------------------------------- */
496 tree_store_notify_remove (tree_entry
* entry
)
498 hook_t
*p
= remove_entry_hooks
;
499 tree_store_remove_fn r
;
503 r
= (tree_store_remove_fn
) p
->hook_fn
;
504 r (entry
, p
->hook_data
);
509 /* --------------------------------------------------------------------------------------------- */
512 remove_entry (tree_entry
* entry
)
514 tree_entry
*current
= entry
->prev
;
516 tree_entry
*ret
= NULL
;
518 tree_store_notify_remove (entry
);
520 /* Correct the submasks of the previous entries */
522 submask
= entry
->next
->submask
;
523 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 */
543 g_free (entry
->name
);
549 /* --------------------------------------------------------------------------------------------- */
552 process_special_dirs (GList
** special_dirs
, char *file
)
554 gchar
**buffers
, **start_buff
;
558 cfg
= mc_config_init (file
);
562 start_buff
= buffers
= mc_config_get_string_list (cfg
, "Special dirs", "list", &buffers_len
);
565 while (*buffers
!= NULL
)
567 *special_dirs
= g_list_prepend (*special_dirs
, *buffers
);
571 g_strfreev (start_buff
);
573 mc_config_deinit (cfg
);
576 /* --------------------------------------------------------------------------------------------- */
579 should_skip_directory (const char *dir
)
581 static GList
*special_dirs
= NULL
;
583 static gboolean loaded
= FALSE
;
589 process_special_dirs (&special_dirs
, profile_name
);
590 process_special_dirs (&special_dirs
, global_profile_name
);
593 for (l
= special_dirs
; l
!= NULL
; l
= g_list_next (l
))
594 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
600 /* --------------------------------------------------------------------------------------------- */
601 /*** public functions ****************************************************************************/
602 /* --------------------------------------------------------------------------------------------- */
604 /* Searches for specified directory */
606 tree_store_whereis (const char *name
)
608 tree_entry
*current
= ts
.tree_first
;
611 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
612 current
= current
->next
;
620 /* --------------------------------------------------------------------------------------------- */
623 tree_store_get (void)
628 /* --------------------------------------------------------------------------------------------- */
630 * \fn int tree_store_load(void)
631 * \brief Loads the tree from the default location
632 * \return 1 if success (true), 0 otherwise (false)
636 tree_store_load (void)
641 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
642 retval
= tree_store_load_from (name
);
648 /* --------------------------------------------------------------------------------------------- */
650 * \fn int tree_store_save(void)
651 * \brief Saves the tree to the default file in an atomic fashion
652 * \return 0 if success, errno on error
656 tree_store_save (void)
661 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
662 mc_util_make_backup_if_possible (name
, ".tmp");
664 retval
= tree_store_save_to (name
);
667 mc_util_restore_from_backup_if_possible (name
, ".tmp");
672 mc_util_unlink_backup_if_possible (name
, ".tmp");
677 /* --------------------------------------------------------------------------------------------- */
680 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
682 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
685 /* --------------------------------------------------------------------------------------------- */
688 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
690 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
694 /* --------------------------------------------------------------------------------------------- */
697 tree_store_remove_entry (const char *name
)
699 tree_entry
*current
, *base
, *old
;
702 g_return_if_fail (name
!= NULL
);
704 /* Miguel Ugly hack */
705 if (name
[0] == PATH_SEP
&& name
[1] == 0)
707 /* Miguel Ugly hack end */
709 base
= tree_store_whereis (name
);
711 return; /* Doesn't exist */
713 len
= strlen (base
->name
);
714 current
= base
->next
;
716 && strncmp (current
->name
, base
->name
, len
) == 0
717 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
))
720 current
= current
->next
;
724 tree_store_dirty (TRUE
);
729 /* --------------------------------------------------------------------------------------------- */
730 /** This subdirectory exists -> clear deletion mark */
733 tree_store_mark_checked (const char *subname
)
736 tree_entry
*current
, *base
;
741 if (ts
.check_name
== NULL
)
744 /* Calculate the full name of the subdirectory */
745 if (subname
[0] == '.' && (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
747 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
748 name
= g_strconcat (PATH_SEP_STR
, subname
, (char *) NULL
);
750 name
= concat_dir_and_file (ts
.check_name
, subname
);
752 /* Search for the subdirectory */
753 current
= ts
.check_start
;
754 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
755 current
= current
->next
;
759 /* Doesn't exist -> add it */
760 current
= tree_store_add_entry (name
);
761 ts
.add_queue
= g_list_prepend (ts
.add_queue
, g_strdup (name
));
765 /* Clear the deletion mark from the subdirectory and its children */
769 len
= strlen (base
->name
);
771 current
= base
->next
;
773 && strncmp (current
->name
, base
->name
, len
) == 0
774 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
777 current
= current
->next
;
782 /* --------------------------------------------------------------------------------------------- */
783 /** Mark the subdirectories of the current directory for delete */
786 tree_store_start_check (const char *path
)
788 tree_entry
*current
, *retval
;
794 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
795 ts
.check_start
= NULL
;
797 /* Search for the start of subdirectories */
798 current
= tree_store_whereis (path
);
803 if (mc_stat (path
, &s
) == -1)
806 if (!S_ISDIR (s
.st_mode
))
809 current
= tree_store_add_entry (path
);
810 ts
.check_name
= g_strdup (path
);
815 ts
.check_name
= g_strdup (path
);
819 /* Mark old subdirectories for delete */
820 ts
.check_start
= current
->next
;
821 len
= strlen (ts
.check_name
);
823 current
= ts
.check_start
;
825 && strncmp (current
->name
, ts
.check_name
, len
) == 0
826 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
829 current
= current
->next
;
835 /* --------------------------------------------------------------------------------------------- */
836 /** Delete subdirectories which still have the deletion mark */
839 tree_store_end_check (void)
841 tree_entry
*current
, *old
;
848 g_return_if_fail (ts
.check_name
!= NULL
);
850 /* Check delete marks and delete if found */
851 len
= strlen (ts
.check_name
);
853 current
= ts
.check_start
;
855 && strncmp (current
->name
, ts
.check_name
, len
) == 0
856 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
859 current
= current
->next
;
864 /* get the stuff in the scan order */
865 ts
.add_queue
= g_list_reverse (ts
.add_queue
);
866 the_queue
= ts
.add_queue
;
868 g_free (ts
.check_name
);
869 ts
.check_name
= NULL
;
871 g_list_foreach (the_queue
, (GFunc
) g_free
, NULL
);
872 g_list_free (the_queue
);
875 /* --------------------------------------------------------------------------------------------- */
878 tree_store_rescan (const char *dir
)
885 if (should_skip_directory (dir
))
887 entry
= tree_store_add_entry (dir
);
893 entry
= tree_store_start_check (dir
);
898 dirp
= mc_opendir (dir
);
901 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
905 if (dp
->d_name
[0] == '.')
907 if (dp
->d_name
[1] == 0 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
911 full_name
= concat_dir_and_file (dir
, dp
->d_name
);
912 if (mc_lstat (full_name
, &buf
) != -1)
914 if (S_ISDIR (buf
.st_mode
))
915 tree_store_mark_checked (dp
->d_name
);
921 tree_store_end_check ();
927 /* --------------------------------------------------------------------------------------------- */