Ticket #4563: support reget in file move operation.
[midnight-commander.git] / src / filemanager / treestore.c
blobb89e6dcca63537d364f1caabd4854c736734c726
1 /*
2 Tree Store
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-2024
12 Free Software Foundation, Inc.
14 Written by:
15 Janne Kukonlehto, 1994, 1996
16 Norbert Warmuth, 1997
17 Miguel de Icaza, 1996, 1999
18 Slava Zanko <slavazanko@gmail.com>, 2013
19 Andrew Borodin <aborodin@vmail.ru>, 2013
21 This file is part of the Midnight Commander.
23 The Midnight Commander is free software: you can redistribute it
24 and/or modify it under the terms of the GNU General Public License as
25 published by the Free Software Foundation, either version 3 of the License,
26 or (at your option) any later version.
28 The Midnight Commander is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU General Public License for more details.
33 You should have received a copy of the GNU General Public License
34 along with this program. If not, see <http://www.gnu.org/licenses/>.
37 /** \file treestore.c
38 * \brief Source: tree store
40 * Contains a storage of the file system tree representation.
43 #include <config.h>
45 #include <errno.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <unistd.h>
53 #include "lib/global.h"
54 #include "lib/mcconfig.h"
55 #include "lib/vfs/vfs.h"
56 #include "lib/fileloc.h"
57 #include "lib/strutil.h"
58 #include "lib/hook.h"
59 #include "lib/util.h"
61 #include "src/setup.h" /* setup_init() */
63 #include "treestore.h"
65 /*** global variables ****************************************************************************/
67 /*** file scope macro definitions ****************************************************************/
69 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
71 /*** file scope type declarations ****************************************************************/
73 /*** forward declarations (file scope functions) *************************************************/
75 static tree_entry *tree_store_add_entry (const vfs_path_t * name);
77 /*** file scope variables ************************************************************************/
79 static struct TreeStore ts;
81 static hook_t *remove_entry_hooks;
83 /* --------------------------------------------------------------------------------------------- */
84 /*** file scope functions ************************************************************************/
85 /* --------------------------------------------------------------------------------------------- */
87 static inline void
88 tree_store_dirty (gboolean dirty)
90 ts.dirty = dirty;
93 /* --------------------------------------------------------------------------------------------- */
94 /**
96 * @return the number of common bytes in the strings.
99 static size_t
100 str_common (const vfs_path_t *s1_vpath, const vfs_path_t *s2_vpath)
102 size_t result = 0;
103 const char *s1, *s2;
105 s1 = vfs_path_as_str (s1_vpath);
106 s2 = vfs_path_as_str (s2_vpath);
108 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
109 result++;
111 return result;
114 /* --------------------------------------------------------------------------------------------- */
115 /** The directory names are arranged in a single linked list in the same
116 * order as they are displayed. When the tree is displayed the expected
117 * order is like this:
119 * /bin
120 * /etc
121 * /etc/X11
122 * /etc/rc.d
123 * /etc.old/X11
124 * /etc.old/rc.d
125 * /usr
127 * i.e. the required collating sequence when comparing two directory names is
128 * '\0' < PATH_SEP < all-other-characters-in-encoding-order
130 * Since strcmp doesn't fulfil this requirement we use pathcmp when
131 * inserting directory names into the list. The meaning of the return value
132 * of pathcmp and strcmp are the same (an integer less than, equal to, or
133 * greater than zero if p1 is found to be less than, to match, or be greater
134 * than p2.
137 static int
138 pathcmp (const vfs_path_t *p1_vpath, const vfs_path_t *p2_vpath)
140 int ret_val;
141 const char *p1, *p2;
143 p1 = vfs_path_as_str (p1_vpath);
144 p2 = vfs_path_as_str (p2_vpath);
146 for (; *p1 == *p2; p1++, p2++)
147 if (*p1 == '\0')
148 return 0;
150 if (*p1 == '\0')
151 ret_val = -1;
152 else if (*p2 == '\0')
153 ret_val = 1;
154 else if (IS_PATH_SEP (*p1))
155 ret_val = -1;
156 else if (IS_PATH_SEP (*p2))
157 ret_val = 1;
158 else
159 ret_val = (*p1 - *p2);
161 return ret_val;
164 /* --------------------------------------------------------------------------------------------- */
166 static char *
167 decode (char *buffer)
169 char *res, *p, *q;
171 res = g_strdup (buffer);
173 for (p = q = res; *p != '\0'; p++, q++)
175 if (*p == '\n')
177 *q = '\0';
178 return res;
181 if (*p != '\\')
183 *q = *p;
184 continue;
187 p++;
189 switch (*p)
191 case 'n':
192 *q = '\n';
193 break;
194 case '\\':
195 *q = '\\';
196 break;
197 default:
198 break;
202 *q = *p;
204 return res;
207 /* --------------------------------------------------------------------------------------------- */
208 /** Loads the tree store from the specified filename */
210 static int
211 tree_store_load_from (const char *name)
213 FILE *file;
214 char buffer[MC_MAXPATHLEN + 20];
216 g_return_val_if_fail (name != NULL, 0);
218 if (ts.loaded)
219 return 1;
221 file = fopen (name, "r");
223 if (file != NULL
224 && (fgets (buffer, sizeof (buffer), file) == NULL
225 || strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0))
227 fclose (file);
228 file = NULL;
231 if (file != NULL)
233 char oldname[MC_MAXPATHLEN] = "\0";
235 ts.loaded = TRUE;
237 /* File open -> read contents */
238 while (fgets (buffer, MC_MAXPATHLEN, file))
240 tree_entry *e;
241 gboolean scanned;
242 char *lc_name;
244 /* Skip invalid records */
245 if (buffer[0] != '0' && buffer[0] != '1')
246 continue;
248 if (buffer[1] != ':')
249 continue;
251 scanned = buffer[0] == '1';
253 lc_name = decode (buffer + 2);
254 if (!IS_PATH_SEP (lc_name[0]))
256 /* Clear-text decompression */
257 char *s;
259 s = strtok (lc_name, " ");
260 if (s != NULL)
262 char *different;
263 int common;
265 common = atoi (s);
266 different = strtok (NULL, "");
267 if (different != NULL)
269 vfs_path_t *vpath;
271 vpath = vfs_path_from_str (oldname);
272 g_strlcpy (oldname + common, different, sizeof (oldname) - (size_t) common);
273 if (vfs_file_is_local (vpath))
275 vfs_path_t *tmp_vpath;
277 tmp_vpath = vfs_path_from_str (oldname);
278 e = tree_store_add_entry (tmp_vpath);
279 vfs_path_free (tmp_vpath, TRUE);
280 e->scanned = scanned;
282 vfs_path_free (vpath, TRUE);
286 else
288 vfs_path_t *vpath;
290 vpath = vfs_path_from_str (lc_name);
291 if (vfs_file_is_local (vpath))
293 e = tree_store_add_entry (vpath);
294 e->scanned = scanned;
296 vfs_path_free (vpath, TRUE);
297 g_strlcpy (oldname, lc_name, sizeof (oldname));
299 g_free (lc_name);
302 fclose (file);
305 /* Nothing loaded, we add some standard directories */
306 if (!ts.tree_first)
308 vfs_path_t *tmp_vpath;
310 tmp_vpath = vfs_path_from_str (PATH_SEP_STR);
311 tree_store_add_entry (tmp_vpath);
312 tree_store_rescan (tmp_vpath);
313 vfs_path_free (tmp_vpath, TRUE);
314 ts.loaded = TRUE;
317 return 1;
320 /* --------------------------------------------------------------------------------------------- */
322 static char *
323 encode (const vfs_path_t *vpath, size_t offset)
325 return str_escape (vfs_path_as_str (vpath) + offset, -1, "\n\\", FALSE);
328 /* --------------------------------------------------------------------------------------------- */
329 /** Saves the tree to the specified filename */
331 static int
332 tree_store_save_to (char *name)
334 tree_entry *current;
335 FILE *file;
337 file = fopen (name, "w");
338 if (file == NULL)
339 return errno;
341 fprintf (file, "%s\n", TREE_SIGNATURE);
343 for (current = ts.tree_first; current != NULL; current = current->next)
344 if (vfs_file_is_local (current->name))
346 int i, common;
348 /* Clear-text compression */
349 if (current->prev != NULL
350 && (common = str_common (current->prev->name, current->name)) > 2)
352 char *encoded;
354 encoded = encode (current->name, common);
355 i = fprintf (file, "%d:%d %s\n", current->scanned ? 1 : 0, common, encoded);
356 g_free (encoded);
358 else
360 char *encoded;
362 encoded = encode (current->name, 0);
363 i = fprintf (file, "%d:%s\n", current->scanned ? 1 : 0, encoded);
364 g_free (encoded);
367 if (i == EOF)
369 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
370 name, unix_error_string (errno));
371 break;
375 tree_store_dirty (FALSE);
376 fclose (file);
378 return 0;
381 /* --------------------------------------------------------------------------------------------- */
383 static tree_entry *
384 tree_store_add_entry (const vfs_path_t *name)
386 int flag = -1;
387 tree_entry *current;
388 tree_entry *old = NULL;
389 tree_entry *new;
390 int submask = 0;
392 if (ts.tree_last != NULL && ts.tree_last->next != NULL)
393 abort ();
395 /* Search for the correct place */
396 for (current = ts.tree_first;
397 current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
398 old = current;
400 if (flag == 0)
401 return current; /* Already in the list */
403 /* Not in the list -> add it */
404 new = g_new0 (tree_entry, 1);
405 if (current == NULL)
407 /* Append to the end of the list */
408 if (ts.tree_first == NULL)
410 /* Empty list */
411 ts.tree_first = new;
412 new->prev = NULL;
414 else
416 if (old != NULL)
417 old->next = new;
418 new->prev = old;
420 new->next = NULL;
421 ts.tree_last = new;
423 else
425 /* Insert in to the middle of the list */
426 new->prev = old;
427 if (old != NULL)
429 /* Yes, in the middle */
430 new->next = old->next;
431 old->next = new;
433 else
435 /* Nope, in the beginning of the list */
436 new->next = ts.tree_first;
437 ts.tree_first = new;
439 new->next->prev = new;
442 /* Calculate attributes */
443 new->name = vfs_path_clone (name);
444 new->sublevel = vfs_path_tokens_count (new->name);
447 const char *new_name;
449 new_name = vfs_path_get_last_path_str (new->name);
450 new->subname = strrchr (new_name, PATH_SEP);
451 if (new->subname == NULL)
452 new->subname = new_name;
453 else
454 new->subname++;
457 if (new->next != NULL)
458 submask = new->next->submask;
460 submask |= 1 << new->sublevel;
461 submask &= (2 << new->sublevel) - 1;
462 new->submask = submask;
463 new->mark = FALSE;
465 /* Correct the submasks of the previous entries */
466 for (current = new->prev;
467 current != NULL && current->sublevel > new->sublevel; current = current->prev)
468 current->submask |= 1 << new->sublevel;
470 tree_store_dirty (TRUE);
471 return new;
474 /* --------------------------------------------------------------------------------------------- */
476 static void
477 tree_store_notify_remove (tree_entry *entry)
479 hook_t *p;
481 for (p = remove_entry_hooks; p != NULL; p = p->next)
483 tree_store_remove_fn r = (tree_store_remove_fn) p->hook_fn;
485 r (entry, p->hook_data);
489 /* --------------------------------------------------------------------------------------------- */
491 static tree_entry *
492 remove_entry (tree_entry *entry)
494 tree_entry *current = entry->prev;
495 long submask = 0;
496 tree_entry *ret = NULL;
498 tree_store_notify_remove (entry);
500 /* Correct the submasks of the previous entries */
501 if (entry->next != NULL)
502 submask = entry->next->submask;
504 for (; current != NULL && current->sublevel > entry->sublevel; current = current->prev)
506 submask |= 1 << current->sublevel;
507 submask &= (2 << current->sublevel) - 1;
508 current->submask = submask;
511 /* Unlink the entry from the list */
512 if (entry->prev != NULL)
513 entry->prev->next = entry->next;
514 else
515 ts.tree_first = entry->next;
517 if (entry->next != NULL)
518 entry->next->prev = entry->prev;
519 else
520 ts.tree_last = entry->prev;
522 /* Free the memory used by the entry */
523 vfs_path_free (entry->name, TRUE);
524 g_free (entry);
526 return ret;
529 /* --------------------------------------------------------------------------------------------- */
531 static void
532 process_special_dirs (GList **special_dirs, const char *file)
534 gchar **start_buff;
535 mc_config_t *cfg;
537 cfg = mc_config_init (file, TRUE);
538 if (cfg == NULL)
539 return;
541 start_buff = mc_config_get_string_list (cfg, "Special dirs", "list", NULL);
542 if (start_buff != NULL)
544 gchar **buffers;
546 for (buffers = start_buff; *buffers != NULL; buffers++)
548 *special_dirs = g_list_prepend (*special_dirs, *buffers);
549 *buffers = NULL;
552 g_strfreev (start_buff);
554 mc_config_deinit (cfg);
557 /* --------------------------------------------------------------------------------------------- */
559 static gboolean
560 should_skip_directory (const vfs_path_t *vpath)
562 static GList *special_dirs = NULL;
563 GList *l;
564 static gboolean loaded = FALSE;
565 gboolean ret = FALSE;
567 if (!loaded)
569 const char *profile_name;
571 profile_name = setup_init ();
572 process_special_dirs (&special_dirs, profile_name);
573 process_special_dirs (&special_dirs, mc_global.profile_name);
575 loaded = TRUE;
578 for (l = special_dirs; l != NULL; l = g_list_next (l))
579 if (strncmp (vfs_path_as_str (vpath), l->data, strlen (l->data)) == 0)
581 ret = TRUE;
582 break;
585 return ret;
588 /* --------------------------------------------------------------------------------------------- */
590 static void
591 queue_vpath_free (gpointer data)
593 vfs_path_free ((vfs_path_t *) data, TRUE);
596 /* --------------------------------------------------------------------------------------------- */
597 /*** public functions ****************************************************************************/
598 /* --------------------------------------------------------------------------------------------- */
600 /* Searches for specified directory */
601 tree_entry *
602 tree_store_whereis (const vfs_path_t *name)
604 tree_entry *current;
605 int flag = -1;
607 for (current = ts.tree_first;
608 current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
611 return flag == 0 ? current : NULL;
614 /* --------------------------------------------------------------------------------------------- */
616 struct TreeStore *
617 tree_store_get (void)
619 return &ts;
622 /* --------------------------------------------------------------------------------------------- */
624 * \fn int tree_store_load(void)
625 * \brief Loads the tree from the default location
626 * \return 1 if success (true), 0 otherwise (false)
630 tree_store_load (void)
632 char *name;
633 int retval;
635 name = mc_config_get_full_path (MC_TREESTORE_FILE);
636 retval = tree_store_load_from (name);
637 g_free (name);
639 return retval;
642 /* --------------------------------------------------------------------------------------------- */
644 * \fn int tree_store_save(void)
645 * \brief Saves the tree to the default file in an atomic fashion
646 * \return 0 if success, errno on error
650 tree_store_save (void)
652 char *name;
653 int retval;
655 name = mc_config_get_full_path (MC_TREESTORE_FILE);
656 mc_util_make_backup_if_possible (name, ".tmp");
658 retval = tree_store_save_to (name);
659 if (retval != 0)
660 mc_util_restore_from_backup_if_possible (name, ".tmp");
661 else
662 mc_util_unlink_backup_if_possible (name, ".tmp");
664 g_free (name);
665 return retval;
668 /* --------------------------------------------------------------------------------------------- */
670 void
671 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
673 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
676 /* --------------------------------------------------------------------------------------------- */
678 void
679 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
681 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
684 /* --------------------------------------------------------------------------------------------- */
686 void
687 tree_store_remove_entry (const vfs_path_t *name_vpath)
689 tree_entry *current, *base;
690 size_t len;
692 g_return_if_fail (name_vpath != NULL);
694 /* Miguel Ugly hack */
696 gboolean is_root;
697 const char *name_vpath_str;
699 name_vpath_str = vfs_path_as_str (name_vpath);
700 is_root = (IS_PATH_SEP (name_vpath_str[0]) && name_vpath_str[1] == '\0');
701 if (is_root)
702 return;
704 /* Miguel Ugly hack end */
706 base = tree_store_whereis (name_vpath);
707 if (base == NULL)
708 return; /* Doesn't exist */
710 len = vfs_path_len (base->name);
711 current = base->next;
712 while (current != NULL && vfs_path_equal_len (current->name, base->name, len))
714 gboolean ok;
715 tree_entry *old;
716 const char *cname;
718 cname = vfs_path_as_str (current->name);
719 ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]));
720 if (!ok)
721 break;
723 old = current;
724 current = current->next;
725 remove_entry (old);
727 remove_entry (base);
728 tree_store_dirty (TRUE);
731 /* --------------------------------------------------------------------------------------------- */
732 /** This subdirectory exists -> clear deletion mark */
734 void
735 tree_store_mark_checked (const char *subname)
737 vfs_path_t *name;
738 tree_entry *current, *base;
739 int flag = 1;
740 const char *cname;
742 if (!ts.loaded)
743 return;
745 if (ts.check_name == NULL)
746 return;
748 /* Calculate the full name of the subdirectory */
749 if (DIR_IS_DOT (subname) || DIR_IS_DOTDOT (subname))
750 return;
752 cname = vfs_path_as_str (ts.check_name);
753 if (IS_PATH_SEP (cname[0]) && cname[1] == '\0')
754 name = vfs_path_build_filename (PATH_SEP_STR, subname, (char *) NULL);
755 else
756 name = vfs_path_append_new (ts.check_name, subname, (char *) NULL);
758 /* Search for the subdirectory */
759 for (current = ts.check_start;
760 current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
763 if (flag != 0)
765 /* Doesn't exist -> add it */
766 current = tree_store_add_entry (name);
767 ts.add_queue_vpath = g_list_prepend (ts.add_queue_vpath, name);
769 else
770 vfs_path_free (name, TRUE);
772 /* Clear the deletion mark from the subdirectory and its children */
773 base = current;
774 if (base != NULL)
776 size_t len;
778 len = vfs_path_len (base->name);
779 base->mark = FALSE;
780 for (current = base->next;
781 current != NULL && vfs_path_equal_len (current->name, base->name, len);
782 current = current->next)
784 gboolean ok;
786 cname = vfs_path_as_str (current->name);
787 ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
788 if (!ok)
789 break;
791 current->mark = FALSE;
796 /* --------------------------------------------------------------------------------------------- */
797 /** Mark the subdirectories of the current directory for delete */
799 tree_entry *
800 tree_store_start_check (const vfs_path_t *vpath)
802 tree_entry *current, *retval;
803 size_t len;
805 if (!ts.loaded)
806 return NULL;
808 g_return_val_if_fail (ts.check_name == NULL, NULL);
809 ts.check_start = NULL;
811 /* Search for the start of subdirectories */
812 current = tree_store_whereis (vpath);
813 if (current == NULL)
815 struct stat s;
817 if (mc_stat (vpath, &s) == -1 || !S_ISDIR (s.st_mode))
818 return NULL;
820 current = tree_store_add_entry (vpath);
821 ts.check_name = vfs_path_clone (vpath);
823 return current;
826 ts.check_name = vfs_path_clone (vpath);
828 retval = current;
830 /* Mark old subdirectories for delete */
831 ts.check_start = current->next;
832 len = vfs_path_len (ts.check_name);
834 for (current = ts.check_start;
835 current != NULL && vfs_path_equal_len (current->name, ts.check_name, len);
836 current = current->next)
838 gboolean ok;
839 const char *cname;
841 cname = vfs_path_as_str (current->name);
842 ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
843 if (!ok)
844 break;
846 current->mark = TRUE;
849 return retval;
852 /* --------------------------------------------------------------------------------------------- */
853 /** Delete subdirectories which still have the deletion mark */
855 void
856 tree_store_end_check (void)
858 tree_entry *current;
859 size_t len;
860 GList *the_queue;
862 if (!ts.loaded)
863 return;
865 g_return_if_fail (ts.check_name != NULL);
867 /* Check delete marks and delete if found */
868 len = vfs_path_len (ts.check_name);
870 current = ts.check_start;
871 while (current != NULL && vfs_path_equal_len (current->name, ts.check_name, len))
873 gboolean ok;
874 tree_entry *old;
875 const char *cname;
877 cname = vfs_path_as_str (current->name);
878 ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
879 if (!ok)
880 break;
882 old = current;
883 current = current->next;
884 if (old->mark)
885 remove_entry (old);
888 /* get the stuff in the scan order */
889 the_queue = g_list_reverse (ts.add_queue_vpath);
890 ts.add_queue_vpath = NULL;
891 vfs_path_free (ts.check_name, TRUE);
892 ts.check_name = NULL;
894 g_list_free_full (the_queue, queue_vpath_free);
897 /* --------------------------------------------------------------------------------------------- */
899 tree_entry *
900 tree_store_rescan (const vfs_path_t *vpath)
902 DIR *dirp;
903 struct stat buf;
904 tree_entry *entry;
906 if (should_skip_directory (vpath))
908 entry = tree_store_add_entry (vpath);
909 entry->scanned = TRUE;
910 return entry;
913 entry = tree_store_start_check (vpath);
914 if (entry == NULL)
915 return NULL;
917 dirp = mc_opendir (vpath);
918 if (dirp != NULL)
920 struct vfs_dirent *dp;
922 for (dp = mc_readdir (dirp); dp != NULL; dp = mc_readdir (dirp))
923 if (!DIR_IS_DOT (dp->d_name) && !DIR_IS_DOTDOT (dp->d_name))
925 vfs_path_t *tmp_vpath;
927 tmp_vpath = vfs_path_append_new (vpath, dp->d_name, (char *) NULL);
928 if (mc_lstat (tmp_vpath, &buf) != -1 && S_ISDIR (buf.st_mode))
929 tree_store_mark_checked (dp->d_name);
930 vfs_path_free (tmp_vpath, TRUE);
933 mc_closedir (dirp);
935 tree_store_end_check ();
936 entry->scanned = TRUE;
938 return entry;
941 /* --------------------------------------------------------------------------------------------- */