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"
55 #include "treestore.h"
58 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
60 static struct TreeStore ts
;
62 static tree_entry
*tree_store_add_entry (const char *name
);
65 tree_store_dirty (int state
)
70 /* Returns the number of common bytes in the strings. */
72 str_common (const char *s1
, const char *s2
)
76 while (*s1
!= '\0' && *s2
!= '\0' && *s1
++ == *s2
++)
81 /* The directory names are arranged in a single linked list in the same
82 order as they are displayed. When the tree is displayed the expected
93 i.e. the required collating sequence when comparing two directory names is
94 '\0' < PATH_SEP < all-other-characters-in-encoding-order
96 Since strcmp doesn't fulfil this requirement we use pathcmp when
97 inserting directory names into the list. The meaning of the return value
98 of pathcmp and strcmp are the same (an integer less than, equal to, or
99 greater than zero if p1 is found to be less than, to match, or be greater
103 pathcmp (const char *p1
, const char *p2
)
105 for (; *p1
== *p2
; p1
++, p2
++)
120 /* Searches for specified directory */
122 tree_store_whereis (const char *name
)
124 tree_entry
*current
= ts
.tree_first
;
127 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
128 current
= current
->next
;
137 tree_store_get (void)
143 decode (char *buffer
)
145 char *res
= g_strdup (buffer
);
148 for (p
= q
= res
; *p
; p
++, q
++)
179 /* Loads the tree store from the specified filename */
181 tree_store_load_from (char *name
)
184 char buffer
[MC_MAXPATHLEN
+ 20], oldname
[MC_MAXPATHLEN
];
189 g_return_val_if_fail (name
!= NULL
, FALSE
);
194 file
= fopen (name
, "r");
198 if (fgets (buffer
, sizeof (buffer
), file
) != NULL
)
200 if (strncmp (buffer
, TREE_SIGNATURE
, strlen (TREE_SIGNATURE
)) != 0)
218 /* File open -> read contents */
220 while (fgets (buffer
, MC_MAXPATHLEN
, file
))
226 /* Skip invalid records */
227 if ((buffer
[0] != '0' && buffer
[0] != '1'))
230 if (buffer
[1] != ':')
233 scanned
= buffer
[0] == '1';
235 lc_name
= decode (buffer
+ 2);
237 len
= strlen (lc_name
);
238 if (lc_name
[0] != PATH_SEP
)
240 /* Clear-text decompression */
241 char *s
= strtok (lc_name
, " ");
246 different
= strtok (NULL
, "");
249 strcpy (oldname
+ common
, different
);
250 if (vfs_file_is_local (oldname
))
252 e
= tree_store_add_entry (oldname
);
253 e
->scanned
= scanned
;
260 if (vfs_file_is_local (lc_name
))
262 e
= tree_store_add_entry (lc_name
);
263 e
->scanned
= scanned
;
265 strcpy (oldname
, lc_name
);
272 /* Nothing loaded, we add some standard directories */
275 tree_store_add_entry (PATH_SEP_STR
);
276 tree_store_rescan (PATH_SEP_STR
);
284 * \fn int tree_store_load(void)
285 * \brief Loads the tree from the default location
286 * \return 1 if success (true), 0 otherwise (false)
289 tree_store_load (void)
294 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
295 retval
= tree_store_load_from (name
);
302 encode (const char *string
)
309 for (special_chars
= 0, p
= string
; *p
; p
++)
311 if (*p
== '\n' || *p
== '\\')
315 res
= g_malloc (p
- string
+ special_chars
+ 1);
316 for (p
= string
, q
= res
; *p
; p
++, q
++)
318 if (*p
!= '\n' && *p
!= '\\')
341 /* Saves the tree to the specified filename */
343 tree_store_save_to (char *name
)
348 file
= fopen (name
, "w");
352 fprintf (file
, "%s\n", TREE_SIGNATURE
);
354 current
= ts
.tree_first
;
359 if (vfs_file_is_local (current
->name
))
361 /* Clear-text compression */
362 if (current
->prev
&& (common
= str_common (current
->prev
->name
, current
->name
)) > 2)
364 char *encoded
= encode (current
->name
+ common
);
366 i
= fprintf (file
, "%d:%d %s\n", current
->scanned
, common
, encoded
);
371 char *encoded
= encode (current
->name
);
373 i
= fprintf (file
, "%d:%s\n", current
->scanned
, encoded
);
379 fprintf (stderr
, _("Cannot write to the %s file:\n%s\n"),
380 name
, unix_error_string (errno
));
384 current
= current
->next
;
386 tree_store_dirty (FALSE
);
393 * \fn int tree_store_save(void)
394 * \brief Saves the tree to the default file in an atomic fashion
395 * \return 0 if success, errno on error
398 tree_store_save (void)
403 name
= g_build_filename (home_dir
, MC_USERCONF_DIR
, MC_TREESTORE_FILE
, NULL
);
404 mc_util_make_backup_if_possible (name
, ".tmp");
406 retval
= tree_store_save_to (name
);
409 mc_util_restore_from_backup_if_possible (name
, ".tmp");
414 mc_util_unlink_backup_if_possible (name
, ".tmp");
420 tree_store_add_entry (const char *name
)
423 tree_entry
*current
= ts
.tree_first
;
424 tree_entry
*old
= NULL
;
429 if (ts
.tree_last
&& ts
.tree_last
->next
)
432 /* Search for the correct place */
433 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
436 current
= current
->next
;
440 return current
; /* Already in the list */
442 /* Not in the list -> add it */
443 new = g_new0 (tree_entry
, 1);
446 /* Append to the end of the list */
463 /* Insert in to the middle of the list */
467 /* Yes, in the middle */
468 new->next
= old
->next
;
473 /* Nope, in the beginning of the list */
474 new->next
= ts
.tree_first
;
477 new->next
->prev
= new;
480 /* Calculate attributes */
481 new->name
= g_strdup (name
);
482 len
= strlen (new->name
);
484 for (i
= 0; i
< len
; i
++)
485 if (new->name
[i
] == PATH_SEP
)
488 new->subname
= new->name
+ i
+ 1;
491 submask
= new->next
->submask
;
494 submask
|= 1 << new->sublevel
;
495 submask
&= (2 << new->sublevel
) - 1;
496 new->submask
= submask
;
499 /* Correct the submasks of the previous entries */
501 while (current
&& current
->sublevel
> new->sublevel
)
503 current
->submask
|= 1 << new->sublevel
;
504 current
= current
->prev
;
507 /* The entry has now been added */
509 if (new->sublevel
> 1)
511 /* Let's check if the parent directory is in the tree */
512 char *parent
= g_strdup (new->name
);
514 for (i
= strlen (parent
) - 1; i
> 1; i
--)
516 if (parent
[i
] == PATH_SEP
)
519 tree_store_add_entry (parent
);
526 tree_store_dirty (TRUE
);
530 static Hook
*remove_entry_hooks
;
533 tree_store_add_entry_remove_hook (tree_store_remove_fn callback
, void *data
)
535 add_hook (&remove_entry_hooks
, (void (*)(void *)) callback
, data
);
539 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback
)
541 delete_hook (&remove_entry_hooks
, (void (*)(void *)) callback
);
545 tree_store_notify_remove (tree_entry
* entry
)
547 Hook
*p
= remove_entry_hooks
;
548 tree_store_remove_fn r
;
552 r
= (tree_store_remove_fn
) p
->hook_fn
;
553 r (entry
, p
->hook_data
);
559 remove_entry (tree_entry
* entry
)
561 tree_entry
*current
= entry
->prev
;
563 tree_entry
*ret
= NULL
;
565 tree_store_notify_remove (entry
);
567 /* Correct the submasks of the previous entries */
569 submask
= entry
->next
->submask
;
570 while (current
&& current
->sublevel
> entry
->sublevel
)
572 submask
|= 1 << current
->sublevel
;
573 submask
&= (2 << current
->sublevel
) - 1;
574 current
->submask
= submask
;
575 current
= current
->prev
;
578 /* Unlink the entry from the list */
580 entry
->prev
->next
= entry
->next
;
582 ts
.tree_first
= entry
->next
;
585 entry
->next
->prev
= entry
->prev
;
587 ts
.tree_last
= entry
->prev
;
589 /* Free the memory used by the entry */
590 g_free (entry
->name
);
597 tree_store_remove_entry (const char *name
)
599 tree_entry
*current
, *base
, *old
;
602 g_return_if_fail (name
!= NULL
);
604 /* Miguel Ugly hack */
605 if (name
[0] == PATH_SEP
&& name
[1] == 0)
607 /* Miguel Ugly hack end */
609 base
= tree_store_whereis (name
);
611 return; /* Doesn't exist */
613 len
= strlen (base
->name
);
614 current
= base
->next
;
616 && strncmp (current
->name
, base
->name
, len
) == 0
617 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
))
620 current
= current
->next
;
624 tree_store_dirty (TRUE
);
629 /* This subdirectory exists -> clear deletion mark */
631 tree_store_mark_checked (const char *subname
)
634 tree_entry
*current
, *base
;
639 if (ts
.check_name
== NULL
)
642 /* Calculate the full name of the subdirectory */
643 if (subname
[0] == '.' && (subname
[1] == 0 || (subname
[1] == '.' && subname
[2] == 0)))
645 if (ts
.check_name
[0] == PATH_SEP
&& ts
.check_name
[1] == 0)
646 name
= g_strconcat (PATH_SEP_STR
, subname
, (char *) NULL
);
648 name
= concat_dir_and_file (ts
.check_name
, subname
);
650 /* Search for the subdirectory */
651 current
= ts
.check_start
;
652 while (current
&& (flag
= pathcmp (current
->name
, name
)) < 0)
653 current
= current
->next
;
657 /* Doesn't exist -> add it */
658 current
= tree_store_add_entry (name
);
659 ts
.add_queue
= g_list_prepend (ts
.add_queue
, g_strdup (name
));
663 /* Clear the deletion mark from the subdirectory and its children */
667 len
= strlen (base
->name
);
669 current
= base
->next
;
671 && strncmp (current
->name
, base
->name
, len
) == 0
672 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
675 current
= current
->next
;
680 /* Mark the subdirectories of the current directory for delete */
682 tree_store_start_check (const char *path
)
684 tree_entry
*current
, *retval
;
690 g_return_val_if_fail (ts
.check_name
== NULL
, NULL
);
691 ts
.check_start
= NULL
;
693 /* Search for the start of subdirectories */
694 current
= tree_store_whereis (path
);
699 if (mc_stat (path
, &s
) == -1)
702 if (!S_ISDIR (s
.st_mode
))
705 current
= tree_store_add_entry (path
);
706 ts
.check_name
= g_strdup (path
);
711 ts
.check_name
= g_strdup (path
);
715 /* Mark old subdirectories for delete */
716 ts
.check_start
= current
->next
;
717 len
= strlen (ts
.check_name
);
719 current
= ts
.check_start
;
721 && strncmp (current
->name
, ts
.check_name
, len
) == 0
722 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
725 current
= current
->next
;
731 /* Delete subdirectories which still have the deletion mark */
733 tree_store_end_check (void)
735 tree_entry
*current
, *old
;
737 GList
*the_queue
, *l
;
742 g_return_if_fail (ts
.check_name
!= NULL
);
744 /* Check delete marks and delete if found */
745 len
= strlen (ts
.check_name
);
747 current
= ts
.check_start
;
749 && strncmp (current
->name
, ts
.check_name
, len
) == 0
750 && (current
->name
[len
] == '\0' || current
->name
[len
] == PATH_SEP
|| len
== 1))
753 current
= current
->next
;
758 /* get the stuff in the scan order */
759 ts
.add_queue
= g_list_reverse (ts
.add_queue
);
760 the_queue
= ts
.add_queue
;
762 g_free (ts
.check_name
);
763 ts
.check_name
= NULL
;
765 for (l
= the_queue
; l
; l
= l
->next
)
770 g_list_free (the_queue
);
774 process_special_dirs (GList
** special_dirs
, char *file
)
776 gchar
**buffers
, **start_buff
;
780 cfg
= mc_config_init (file
);
784 start_buff
= buffers
= mc_config_get_string_list (cfg
, "Special dirs", "list", &buffers_len
);
787 mc_config_deinit (cfg
);
793 *special_dirs
= g_list_prepend (*special_dirs
, g_strdup (*buffers
));
796 g_strfreev (start_buff
);
797 mc_config_deinit (cfg
);
801 should_skip_directory (const char *dir
)
803 static GList
*special_dirs
;
811 process_special_dirs (&special_dirs
, profile_name
);
812 process_special_dirs (&special_dirs
, global_profile_name
);
815 for (l
= special_dirs
; l
; l
= l
->next
)
817 if (strncmp (dir
, l
->data
, strlen (l
->data
)) == 0)
824 tree_store_rescan (const char *dir
)
831 if (should_skip_directory (dir
))
833 entry
= tree_store_add_entry (dir
);
839 entry
= tree_store_start_check (dir
);
844 dirp
= mc_opendir (dir
);
847 for (dp
= mc_readdir (dirp
); dp
; dp
= mc_readdir (dirp
))
851 if (dp
->d_name
[0] == '.')
853 if (dp
->d_name
[1] == 0 || (dp
->d_name
[1] == '.' && dp
->d_name
[2] == 0))
857 full_name
= concat_dir_and_file (dir
, dp
->d_name
);
858 if (mc_lstat (full_name
, &buf
) != -1)
860 if (S_ISDIR (buf
.st_mode
))
861 tree_store_mark_checked (dp
->d_name
);
867 tree_store_end_check ();