Ticket #1648: implemented single-line boxes.
[kaloumi3.git] / src / treestore.c
blob2d2c4d6f2357708a4c478302bce1b58d2c824cbd
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++) {
149 if (*p == '\n') {
150 *q = 0;
151 return res;
154 if (*p != '\\') {
155 *q = *p;
156 continue;
159 p++;
161 switch (*p) {
162 case 'n':
163 *q = '\n';
164 break;
165 case '\\':
166 *q = '\\';
167 break;
170 *q = *p;
172 return res;
175 /* Loads the tree store from the specified filename */
176 static int
177 tree_store_load_from(char *name)
179 FILE *file;
180 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
181 char *different;
182 int len, common;
183 int do_load;
185 g_return_val_if_fail(name != NULL, FALSE);
187 if (ts.loaded)
188 return TRUE;
190 file = fopen(name, "r");
192 if (file) {
193 fgets(buffer, sizeof(buffer), file);
195 if (strncmp(buffer, TREE_SIGNATURE, strlen(TREE_SIGNATURE)) != 0) {
196 fclose(file);
197 do_load = FALSE;
198 } else
199 do_load = TRUE;
200 } else
201 do_load = FALSE;
203 if (do_load) {
204 ts.loaded = TRUE;
206 /* File open -> read contents */
207 oldname[0] = 0;
208 while (fgets(buffer, MC_MAXPATHLEN, file)) {
209 tree_entry *e;
210 int scanned;
211 char *lc_name;
213 /* Skip invalid records */
214 if ((buffer[0] != '0' && buffer[0] != '1'))
215 continue;
217 if (buffer[1] != ':')
218 continue;
220 scanned = buffer[0] == '1';
222 lc_name = decode(buffer + 2);
224 len = strlen(lc_name);
225 if (lc_name[0] != PATH_SEP) {
226 /* Clear-text decompression */
227 char *s = strtok(lc_name, " ");
229 if (s) {
230 common = atoi(s);
231 different = strtok(NULL, "");
232 if (different) {
233 strcpy(oldname + common, different);
234 if (vfs_file_is_local(oldname)) {
235 e = tree_store_add_entry(oldname);
236 e->scanned = scanned;
240 } else {
241 if (vfs_file_is_local(lc_name)) {
242 e = tree_store_add_entry(lc_name);
243 e->scanned = scanned;
245 strcpy(oldname, lc_name);
247 g_free(lc_name);
249 fclose(file);
252 /* Nothing loaded, we add some standard directories */
253 if (!ts.tree_first) {
254 tree_store_add_entry(PATH_SEP_STR);
255 tree_store_rescan(PATH_SEP_STR);
256 ts.loaded = TRUE;
259 return TRUE;
263 * \fn int tree_store_load(void)
264 * \brief Loads the tree from the default location
265 * \return 1 if success (true), 0 otherwise (false)
268 tree_store_load(void)
270 char *name;
271 int retval;
273 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
274 retval = tree_store_load_from(name);
275 g_free(name);
277 return retval;
280 static char *
281 encode(const char *string)
283 int special_chars;
284 const char *p;
285 char *q;
286 char *res;
288 for (special_chars = 0, p = string; *p; p++) {
289 if (*p == '\n' || *p == '\\')
290 special_chars++;
293 res = g_malloc(p - string + special_chars + 1);
294 for (p = string, q = res; *p; p++, q++) {
295 if (*p != '\n' && *p != '\\') {
296 *q = *p;
297 continue;
300 *q++ = '\\';
302 switch (*p) {
303 case '\n':
304 *q = 'n';
305 break;
307 case '\\':
308 *q = '\\';
309 break;
312 *q = 0;
313 return res;
316 /* Saves the tree to the specified filename */
317 static int
318 tree_store_save_to(char *name)
320 tree_entry *current;
321 FILE *file;
323 file = fopen(name, "w");
324 if (!file)
325 return errno;
327 fprintf(file, "%s\n", TREE_SIGNATURE);
329 current = ts.tree_first;
330 while (current) {
331 int i, common;
333 if (vfs_file_is_local(current->name)) {
334 /* Clear-text compression */
335 if (current->prev
336 && (common =
337 str_common(current->prev->name, current->name)) > 2) {
338 char *encoded = encode(current->name + common);
340 i = fprintf(file, "%d:%d %s\n", current->scanned, common,
341 encoded);
342 g_free(encoded);
343 } else {
344 char *encoded = encode(current->name);
346 i = fprintf(file, "%d:%s\n", current->scanned, encoded);
347 g_free(encoded);
350 if (i == EOF) {
351 fprintf(stderr, _("Cannot write to the %s file:\n%s\n"),
352 name, unix_error_string(errno));
353 break;
356 current = current->next;
358 tree_store_dirty(FALSE);
359 fclose(file);
361 return 0;
365 * \fn int tree_store_save(void)
366 * \brief Saves the tree to the default file in an atomic fashion
367 * \return 0 if success, errno on error
370 tree_store_save(void)
372 char *name;
373 int retval;
375 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
376 mc_util_make_backup_if_possible (name, ".tmp");
378 if ((retval = tree_store_save_to(name)) != 0) {
379 mc_util_restore_from_backup_if_possible (name, ".tmp");
380 g_free(name);
381 return retval;
384 mc_util_unlink_backup_if_possible (name, ".tmp");
385 g_free(name);
386 return 0;
389 static tree_entry *
390 tree_store_add_entry(const char *name)
392 int flag = -1;
393 tree_entry *current = ts.tree_first;
394 tree_entry *old = NULL;
395 tree_entry *new;
396 int i, len;
397 int submask = 0;
399 if (ts.tree_last && ts.tree_last->next)
400 abort();
402 /* Search for the correct place */
403 while (current && (flag = pathcmp(current->name, name)) < 0) {
404 old = current;
405 current = current->next;
408 if (flag == 0)
409 return current; /* Already in the list */
411 /* Not in the list -> add it */
412 new = g_new0(tree_entry, 1);
413 if (!current) {
414 /* Append to the end of the list */
415 if (!ts.tree_first) {
416 /* Empty list */
417 ts.tree_first = new;
418 new->prev = NULL;
419 } else {
420 old->next = new;
421 new->prev = old;
423 new->next = NULL;
424 ts.tree_last = new;
425 } else {
426 /* Insert in to the middle of the list */
427 new->prev = old;
428 if (old) {
429 /* Yes, in the middle */
430 new->next = old->next;
431 old->next = new;
432 } else {
433 /* Nope, in the beginning of the list */
434 new->next = ts.tree_first;
435 ts.tree_first = new;
437 new->next->prev = new;
440 /* Calculate attributes */
441 new->name = g_strdup(name);
442 len = strlen(new->name);
443 new->sublevel = 0;
444 for (i = 0; i < len; i++)
445 if (new->name[i] == PATH_SEP) {
446 new->sublevel++;
447 new->subname = new->name + i + 1;
449 if (new->next)
450 submask = new->next->submask;
451 else
452 submask = 0;
453 submask |= 1 << new->sublevel;
454 submask &= (2 << new->sublevel) - 1;
455 new->submask = submask;
456 new->mark = 0;
458 /* Correct the submasks of the previous entries */
459 current = new->prev;
460 while (current && current->sublevel > new->sublevel) {
461 current->submask |= 1 << new->sublevel;
462 current = current->prev;
465 /* The entry has now been added */
467 if (new->sublevel > 1) {
468 /* Let's check if the parent directory is in the tree */
469 char *parent = g_strdup(new->name);
471 for (i = strlen(parent) - 1; i > 1; i--) {
472 if (parent[i] == PATH_SEP) {
473 parent[i] = 0;
474 tree_store_add_entry(parent);
475 break;
478 g_free(parent);
481 tree_store_dirty(TRUE);
482 return new;
485 static Hook *remove_entry_hooks;
487 void
488 tree_store_add_entry_remove_hook(tree_store_remove_fn callback, void *data)
490 add_hook(&remove_entry_hooks, (void (*)(void *)) callback, data);
493 void
494 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback)
496 delete_hook(&remove_entry_hooks, (void (*)(void *)) callback);
499 static void
500 tree_store_notify_remove(tree_entry * entry)
502 Hook *p = remove_entry_hooks;
503 tree_store_remove_fn r;
505 while (p) {
506 r = (tree_store_remove_fn) p->hook_fn;
507 r(entry, p->hook_data);
508 p = p->next;
512 static tree_entry *
513 remove_entry(tree_entry * entry)
515 tree_entry *current = entry->prev;
516 long submask = 0;
517 tree_entry *ret = NULL;
519 tree_store_notify_remove(entry);
521 /* Correct the submasks of the previous entries */
522 if (entry->next)
523 submask = entry->next->submask;
524 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 */
532 if (entry->prev)
533 entry->prev->next = entry->next;
534 else
535 ts.tree_first = entry->next;
537 if (entry->next)
538 entry->next->prev = entry->prev;
539 else
540 ts.tree_last = entry->prev;
542 /* Free the memory used by the entry */
543 g_free(entry->name);
544 g_free(entry);
546 return ret;
549 void
550 tree_store_remove_entry(const char *name)
552 tree_entry *current, *base, *old;
553 int len;
555 g_return_if_fail(name != NULL);
557 /* Miguel Ugly hack */
558 if (name[0] == PATH_SEP && name[1] == 0)
559 return;
560 /* Miguel Ugly hack end */
562 base = tree_store_whereis(name);
563 if (!base)
564 return; /* Doesn't exist */
566 len = strlen(base->name);
567 current = base->next;
568 while (current
569 && strncmp(current->name, base->name, len) == 0
570 && (current->name[len] == '\0'
571 || current->name[len] == PATH_SEP)) {
572 old = current;
573 current = current->next;
574 remove_entry(old);
576 remove_entry(base);
577 tree_store_dirty(TRUE);
579 return;
582 /* This subdirectory exists -> clear deletion mark */
583 void
584 tree_store_mark_checked(const char *subname)
586 char *name;
587 tree_entry *current, *base;
588 int flag = 1, len;
589 if (!ts.loaded)
590 return;
592 if (ts.check_name == NULL)
593 return;
595 /* Calculate the full name of the subdirectory */
596 if (subname[0] == '.' &&
597 (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
598 return;
599 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
600 name = g_strconcat(PATH_SEP_STR, subname, (char *) NULL);
601 else
602 name = concat_dir_and_file(ts.check_name, subname);
604 /* Search for the subdirectory */
605 current = ts.check_start;
606 while (current && (flag = pathcmp(current->name, name)) < 0)
607 current = current->next;
609 if (flag != 0) {
610 /* Doesn't exist -> add it */
611 current = tree_store_add_entry(name);
612 ts.add_queue = g_list_prepend(ts.add_queue, g_strdup(name));
614 g_free(name);
616 /* Clear the deletion mark from the subdirectory and its children */
617 base = current;
618 if (base) {
619 len = strlen(base->name);
620 base->mark = 0;
621 current = base->next;
622 while (current
623 && strncmp(current->name, base->name, len) == 0
624 && (current->name[len] == '\0'
625 || current->name[len] == PATH_SEP || len == 1)) {
626 current->mark = 0;
627 current = current->next;
632 /* Mark the subdirectories of the current directory for delete */
633 tree_entry *
634 tree_store_start_check(const char *path)
636 tree_entry *current, *retval;
637 int len;
639 if (!ts.loaded)
640 return NULL;
642 g_return_val_if_fail(ts.check_name == NULL, NULL);
643 ts.check_start = NULL;
645 /* Search for the start of subdirectories */
646 current = tree_store_whereis(path);
647 if (!current) {
648 struct stat s;
650 if (mc_stat(path, &s) == -1)
651 return NULL;
653 if (!S_ISDIR(s.st_mode))
654 return NULL;
656 current = tree_store_add_entry(path);
657 ts.check_name = g_strdup(path);
659 return current;
662 ts.check_name = g_strdup(path);
664 retval = current;
666 /* Mark old subdirectories for delete */
667 ts.check_start = current->next;
668 len = strlen(ts.check_name);
670 current = ts.check_start;
671 while (current
672 && strncmp(current->name, ts.check_name, len) == 0
673 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
674 || len == 1)) {
675 current->mark = 1;
676 current = current->next;
679 return retval;
682 /* Delete subdirectories which still have the deletion mark */
683 void
684 tree_store_end_check(void)
686 tree_entry *current, *old;
687 int len;
688 GList *the_queue, *l;
690 if (!ts.loaded)
691 return;
693 g_return_if_fail(ts.check_name != NULL);
695 /* Check delete marks and delete if found */
696 len = strlen(ts.check_name);
698 current = ts.check_start;
699 while (current
700 && strncmp(current->name, ts.check_name, len) == 0
701 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
702 || len == 1)) {
703 old = current;
704 current = current->next;
705 if (old->mark)
706 remove_entry(old);
709 /* get the stuff in the scan order */
710 ts.add_queue = g_list_reverse(ts.add_queue);
711 the_queue = ts.add_queue;
712 ts.add_queue = NULL;
713 g_free(ts.check_name);
714 ts.check_name = NULL;
716 for (l = the_queue; l; l = l->next) {
717 g_free(l->data);
720 g_list_free(the_queue);
723 static void
724 process_special_dirs(GList ** special_dirs, char *file)
726 gchar **buffers, **start_buff;
727 mc_config_t *cfg;
728 gsize buffers_len;
730 cfg = mc_config_init(file);
731 if (cfg == NULL)
732 return;
734 start_buff = buffers = mc_config_get_string_list(cfg, "Special dirs", "list", &buffers_len);
735 if (buffers == NULL){
736 mc_config_deinit(cfg);
737 return;
740 while(*buffers) {
741 *special_dirs = g_list_prepend(*special_dirs, g_strdup(*buffers));
742 buffers++;
744 g_strfreev(start_buff);
745 mc_config_deinit(cfg);
748 static gboolean
749 should_skip_directory(const char *dir)
751 static GList *special_dirs;
752 GList *l;
753 static int loaded;
755 if (loaded == 0) {
756 loaded = 1;
757 setup_init();
758 process_special_dirs(&special_dirs, profile_name);
759 process_special_dirs(&special_dirs, global_profile_name);
762 for (l = special_dirs; l; l = l->next) {
763 if (strncmp(dir, l->data, strlen(l->data)) == 0)
764 return TRUE;
766 return FALSE;
769 tree_entry *
770 tree_store_rescan(const char *dir)
772 DIR *dirp;
773 struct dirent *dp;
774 struct stat buf;
775 tree_entry *entry;
777 if (should_skip_directory(dir)) {
778 entry = tree_store_add_entry(dir);
779 entry->scanned = 1;
781 return entry;
784 entry = tree_store_start_check(dir);
786 if (!entry)
787 return NULL;
789 dirp = mc_opendir(dir);
790 if (dirp) {
791 for (dp = mc_readdir(dirp); dp; dp = mc_readdir(dirp)) {
792 char *full_name;
794 if (dp->d_name[0] == '.') {
795 if (dp->d_name[1] == 0
796 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
797 continue;
800 full_name = concat_dir_and_file(dir, dp->d_name);
801 if (mc_lstat(full_name, &buf) != -1) {
802 if (S_ISDIR(buf.st_mode))
803 tree_store_mark_checked(dp->d_name);
805 g_free(full_name);
807 mc_closedir(dirp);
809 tree_store_end_check();
810 entry->scanned = 1;
812 return entry;