MC Edit: column_highlighting flag is a member of WEdit struct.
[midnight-commander.git] / src / treestore.c
blob77a2b0e98ada5a1886d2c0051a4877a0ba33261d
1 /*
2 * Tree Store
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
10 1997 Norbert Warmuth
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.
34 /** \file treestore.c
35 * \brief Source: tree store
37 * Contains a storage of the file system tree representation.
40 #include <config.h>
42 #include <errno.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <unistd.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"
56 #include "setup.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);
64 static void
65 tree_store_dirty (int state)
67 ts.dirty = state;
70 /* Returns the number of common bytes in the strings. */
71 static size_t
72 str_common (const char *s1, const char *s2)
74 size_t result = 0;
76 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
77 result++;
78 return result;
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
83 order is like this:
85 /bin
86 /etc
87 /etc/X11
88 /etc/rc.d
89 /etc.old/X11
90 /etc.old/rc.d
91 /usr
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
100 than p2.
102 static int
103 pathcmp (const char *p1, const char *p2)
105 for (; *p1 == *p2; p1++, p2++)
106 if (*p1 == '\0')
107 return 0;
109 if (*p1 == '\0')
110 return -1;
111 if (*p2 == '\0')
112 return 1;
113 if (*p1 == PATH_SEP)
114 return -1;
115 if (*p2 == PATH_SEP)
116 return 1;
117 return (*p1 - *p2);
120 /* Searches for specified directory */
121 tree_entry *
122 tree_store_whereis (const char *name)
124 tree_entry *current = ts.tree_first;
125 int flag = -1;
127 while (current && (flag = pathcmp (current->name, name)) < 0)
128 current = current->next;
130 if (flag == 0)
131 return current;
132 else
133 return NULL;
136 struct TreeStore *
137 tree_store_get (void)
139 return &ts;
142 static char *
143 decode (char *buffer)
145 char *res = g_strdup (buffer);
146 char *p, *q;
148 for (p = q = res; *p; p++, q++)
150 if (*p == '\n')
152 *q = 0;
153 return res;
156 if (*p != '\\')
158 *q = *p;
159 continue;
162 p++;
164 switch (*p)
166 case 'n':
167 *q = '\n';
168 break;
169 case '\\':
170 *q = '\\';
171 break;
174 *q = *p;
176 return res;
179 /* Loads the tree store from the specified filename */
180 static int
181 tree_store_load_from (char *name)
183 FILE *file;
184 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
185 char *different;
186 int len, common;
187 int do_load;
189 g_return_val_if_fail (name != NULL, FALSE);
191 if (ts.loaded)
192 return TRUE;
194 file = fopen (name, "r");
196 if (file)
198 if (fgets (buffer, sizeof (buffer), file) != NULL)
200 if (strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0)
202 fclose (file);
203 do_load = FALSE;
205 else
206 do_load = TRUE;
208 else
209 do_load = FALSE;
211 else
212 do_load = FALSE;
214 if (do_load)
216 ts.loaded = TRUE;
218 /* File open -> read contents */
219 oldname[0] = 0;
220 while (fgets (buffer, MC_MAXPATHLEN, file))
222 tree_entry *e;
223 int scanned;
224 char *lc_name;
226 /* Skip invalid records */
227 if ((buffer[0] != '0' && buffer[0] != '1'))
228 continue;
230 if (buffer[1] != ':')
231 continue;
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, " ");
243 if (s)
245 common = atoi (s);
246 different = strtok (NULL, "");
247 if (different)
249 strcpy (oldname + common, different);
250 if (vfs_file_is_local (oldname))
252 e = tree_store_add_entry (oldname);
253 e->scanned = scanned;
258 else
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);
267 g_free (lc_name);
269 fclose (file);
272 /* Nothing loaded, we add some standard directories */
273 if (!ts.tree_first)
275 tree_store_add_entry (PATH_SEP_STR);
276 tree_store_rescan (PATH_SEP_STR);
277 ts.loaded = TRUE;
280 return TRUE;
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)
291 char *name;
292 int retval;
294 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
295 retval = tree_store_load_from (name);
296 g_free (name);
298 return retval;
301 static char *
302 encode (const char *string)
304 int special_chars;
305 const char *p;
306 char *q;
307 char *res;
309 for (special_chars = 0, p = string; *p; p++)
311 if (*p == '\n' || *p == '\\')
312 special_chars++;
315 res = g_malloc (p - string + special_chars + 1);
316 for (p = string, q = res; *p; p++, q++)
318 if (*p != '\n' && *p != '\\')
320 *q = *p;
321 continue;
324 *q++ = '\\';
326 switch (*p)
328 case '\n':
329 *q = 'n';
330 break;
332 case '\\':
333 *q = '\\';
334 break;
337 *q = 0;
338 return res;
341 /* Saves the tree to the specified filename */
342 static int
343 tree_store_save_to (char *name)
345 tree_entry *current;
346 FILE *file;
348 file = fopen (name, "w");
349 if (!file)
350 return errno;
352 fprintf (file, "%s\n", TREE_SIGNATURE);
354 current = ts.tree_first;
355 while (current)
357 int i, common;
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);
367 g_free (encoded);
369 else
371 char *encoded = encode (current->name);
373 i = fprintf (file, "%d:%s\n", current->scanned, encoded);
374 g_free (encoded);
377 if (i == EOF)
379 fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
380 name, unix_error_string (errno));
381 break;
384 current = current->next;
386 tree_store_dirty (FALSE);
387 fclose (file);
389 return 0;
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)
400 char *name;
401 int retval;
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);
407 if (retval != 0)
409 mc_util_restore_from_backup_if_possible (name, ".tmp");
410 g_free (name);
411 return retval;
414 mc_util_unlink_backup_if_possible (name, ".tmp");
415 g_free (name);
416 return 0;
419 static tree_entry *
420 tree_store_add_entry (const char *name)
422 int flag = -1;
423 tree_entry *current = ts.tree_first;
424 tree_entry *old = NULL;
425 tree_entry *new;
426 int i, len;
427 int submask = 0;
429 if (ts.tree_last && ts.tree_last->next)
430 abort ();
432 /* Search for the correct place */
433 while (current && (flag = pathcmp (current->name, name)) < 0)
435 old = current;
436 current = current->next;
439 if (flag == 0)
440 return current; /* Already in the list */
442 /* Not in the list -> add it */
443 new = g_new0 (tree_entry, 1);
444 if (!current)
446 /* Append to the end of the list */
447 if (!ts.tree_first)
449 /* Empty list */
450 ts.tree_first = new;
451 new->prev = NULL;
453 else
455 old->next = new;
456 new->prev = old;
458 new->next = NULL;
459 ts.tree_last = new;
461 else
463 /* Insert in to the middle of the list */
464 new->prev = old;
465 if (old)
467 /* Yes, in the middle */
468 new->next = old->next;
469 old->next = new;
471 else
473 /* Nope, in the beginning of the list */
474 new->next = ts.tree_first;
475 ts.tree_first = new;
477 new->next->prev = new;
480 /* Calculate attributes */
481 new->name = g_strdup (name);
482 len = strlen (new->name);
483 new->sublevel = 0;
484 for (i = 0; i < len; i++)
485 if (new->name[i] == PATH_SEP)
487 new->sublevel++;
488 new->subname = new->name + i + 1;
490 if (new->next)
491 submask = new->next->submask;
492 else
493 submask = 0;
494 submask |= 1 << new->sublevel;
495 submask &= (2 << new->sublevel) - 1;
496 new->submask = submask;
497 new->mark = 0;
499 /* Correct the submasks of the previous entries */
500 current = new->prev;
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)
518 parent[i] = 0;
519 tree_store_add_entry (parent);
520 break;
523 g_free (parent);
526 tree_store_dirty (TRUE);
527 return new;
530 static Hook *remove_entry_hooks;
532 void
533 tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
535 add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
538 void
539 tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
541 delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
544 static void
545 tree_store_notify_remove (tree_entry * entry)
547 Hook *p = remove_entry_hooks;
548 tree_store_remove_fn r;
550 while (p)
552 r = (tree_store_remove_fn) p->hook_fn;
553 r (entry, p->hook_data);
554 p = p->next;
558 static tree_entry *
559 remove_entry (tree_entry * entry)
561 tree_entry *current = entry->prev;
562 long submask = 0;
563 tree_entry *ret = NULL;
565 tree_store_notify_remove (entry);
567 /* Correct the submasks of the previous entries */
568 if (entry->next)
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 */
579 if (entry->prev)
580 entry->prev->next = entry->next;
581 else
582 ts.tree_first = entry->next;
584 if (entry->next)
585 entry->next->prev = entry->prev;
586 else
587 ts.tree_last = entry->prev;
589 /* Free the memory used by the entry */
590 g_free (entry->name);
591 g_free (entry);
593 return ret;
596 void
597 tree_store_remove_entry (const char *name)
599 tree_entry *current, *base, *old;
600 int len;
602 g_return_if_fail (name != NULL);
604 /* Miguel Ugly hack */
605 if (name[0] == PATH_SEP && name[1] == 0)
606 return;
607 /* Miguel Ugly hack end */
609 base = tree_store_whereis (name);
610 if (!base)
611 return; /* Doesn't exist */
613 len = strlen (base->name);
614 current = base->next;
615 while (current
616 && strncmp (current->name, base->name, len) == 0
617 && (current->name[len] == '\0' || current->name[len] == PATH_SEP))
619 old = current;
620 current = current->next;
621 remove_entry (old);
623 remove_entry (base);
624 tree_store_dirty (TRUE);
626 return;
629 /* This subdirectory exists -> clear deletion mark */
630 void
631 tree_store_mark_checked (const char *subname)
633 char *name;
634 tree_entry *current, *base;
635 int flag = 1, len;
636 if (!ts.loaded)
637 return;
639 if (ts.check_name == NULL)
640 return;
642 /* Calculate the full name of the subdirectory */
643 if (subname[0] == '.' && (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
644 return;
645 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
646 name = g_strconcat (PATH_SEP_STR, subname, (char *) NULL);
647 else
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;
655 if (flag != 0)
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));
661 g_free (name);
663 /* Clear the deletion mark from the subdirectory and its children */
664 base = current;
665 if (base)
667 len = strlen (base->name);
668 base->mark = 0;
669 current = base->next;
670 while (current
671 && strncmp (current->name, base->name, len) == 0
672 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
674 current->mark = 0;
675 current = current->next;
680 /* Mark the subdirectories of the current directory for delete */
681 tree_entry *
682 tree_store_start_check (const char *path)
684 tree_entry *current, *retval;
685 int len;
687 if (!ts.loaded)
688 return NULL;
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);
695 if (!current)
697 struct stat s;
699 if (mc_stat (path, &s) == -1)
700 return NULL;
702 if (!S_ISDIR (s.st_mode))
703 return NULL;
705 current = tree_store_add_entry (path);
706 ts.check_name = g_strdup (path);
708 return current;
711 ts.check_name = g_strdup (path);
713 retval = current;
715 /* Mark old subdirectories for delete */
716 ts.check_start = current->next;
717 len = strlen (ts.check_name);
719 current = ts.check_start;
720 while (current
721 && strncmp (current->name, ts.check_name, len) == 0
722 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
724 current->mark = 1;
725 current = current->next;
728 return retval;
731 /* Delete subdirectories which still have the deletion mark */
732 void
733 tree_store_end_check (void)
735 tree_entry *current, *old;
736 int len;
737 GList *the_queue, *l;
739 if (!ts.loaded)
740 return;
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;
748 while (current
749 && strncmp (current->name, ts.check_name, len) == 0
750 && (current->name[len] == '\0' || current->name[len] == PATH_SEP || len == 1))
752 old = current;
753 current = current->next;
754 if (old->mark)
755 remove_entry (old);
758 /* get the stuff in the scan order */
759 ts.add_queue = g_list_reverse (ts.add_queue);
760 the_queue = ts.add_queue;
761 ts.add_queue = NULL;
762 g_free (ts.check_name);
763 ts.check_name = NULL;
765 for (l = the_queue; l; l = l->next)
767 g_free (l->data);
770 g_list_free (the_queue);
773 static void
774 process_special_dirs (GList ** special_dirs, char *file)
776 gchar **buffers, **start_buff;
777 mc_config_t *cfg;
778 gsize buffers_len;
780 cfg = mc_config_init (file);
781 if (cfg == NULL)
782 return;
784 start_buff = buffers = mc_config_get_string_list (cfg, "Special dirs", "list", &buffers_len);
785 if (buffers == NULL)
787 mc_config_deinit (cfg);
788 return;
791 while (*buffers)
793 *special_dirs = g_list_prepend (*special_dirs, g_strdup (*buffers));
794 buffers++;
796 g_strfreev (start_buff);
797 mc_config_deinit (cfg);
800 static gboolean
801 should_skip_directory (const char *dir)
803 static GList *special_dirs;
804 GList *l;
805 static int loaded;
807 if (loaded == 0)
809 loaded = 1;
810 setup_init ();
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)
818 return TRUE;
820 return FALSE;
823 tree_entry *
824 tree_store_rescan (const char *dir)
826 DIR *dirp;
827 struct dirent *dp;
828 struct stat buf;
829 tree_entry *entry;
831 if (should_skip_directory (dir))
833 entry = tree_store_add_entry (dir);
834 entry->scanned = 1;
836 return entry;
839 entry = tree_store_start_check (dir);
841 if (!entry)
842 return NULL;
844 dirp = mc_opendir (dir);
845 if (dirp)
847 for (dp = mc_readdir (dirp); dp; dp = mc_readdir (dirp))
849 char *full_name;
851 if (dp->d_name[0] == '.')
853 if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
854 continue;
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);
863 g_free (full_name);
865 mc_closedir (dirp);
867 tree_store_end_check ();
868 entry->scanned = 1;
870 return entry;