Ticket #1711: i18n: context and cleanup in file prompt strings
[midnight-commander.git] / src / treestore.c
blob61126dc89349fb9955ad23b1f18ce139f59f72c2
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 "global.h"
51 #include "treestore.h"
52 #include "../src/mcconfig/mcconfig.h"
53 #include "setup.h"
54 #include "fileloc.h"
56 #define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
58 static struct TreeStore ts;
60 static tree_entry *tree_store_add_entry(const char *name);
62 static void
63 tree_store_dirty(int state)
65 ts.dirty = state;
68 /* Returns the number of common bytes in the strings. */
69 static size_t
70 str_common(const char *s1, const char *s2)
72 size_t result = 0;
74 while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
75 result++;
76 return result;
79 /* The directory names are arranged in a single linked list in the same
80 order as they are displayed. When the tree is displayed the expected
81 order is like this:
83 /bin
84 /etc
85 /etc/X11
86 /etc/rc.d
87 /etc.old/X11
88 /etc.old/rc.d
89 /usr
91 i.e. the required collating sequence when comparing two directory names is
92 '\0' < PATH_SEP < all-other-characters-in-encoding-order
94 Since strcmp doesn't fulfil this requirement we use pathcmp when
95 inserting directory names into the list. The meaning of the return value
96 of pathcmp and strcmp are the same (an integer less than, equal to, or
97 greater than zero if p1 is found to be less than, to match, or be greater
98 than p2.
100 static int
101 pathcmp(const char *p1, const char *p2)
103 for (; *p1 == *p2; p1++, p2++)
104 if (*p1 == '\0')
105 return 0;
107 if (*p1 == '\0')
108 return -1;
109 if (*p2 == '\0')
110 return 1;
111 if (*p1 == PATH_SEP)
112 return -1;
113 if (*p2 == PATH_SEP)
114 return 1;
115 return (*p1 - *p2);
118 /* Searches for specified directory */
119 tree_entry *
120 tree_store_whereis(const char *name)
122 tree_entry *current = ts.tree_first;
123 int flag = -1;
125 while (current && (flag = pathcmp(current->name, name)) < 0)
126 current = current->next;
128 if (flag == 0)
129 return current;
130 else
131 return NULL;
134 struct TreeStore *
135 tree_store_get(void)
137 return &ts;
140 static char *
141 decode(char *buffer)
143 char *res = g_strdup(buffer);
144 char *p, *q;
146 for (p = q = res; *p; p++, q++) {
147 if (*p == '\n') {
148 *q = 0;
149 return res;
152 if (*p != '\\') {
153 *q = *p;
154 continue;
157 p++;
159 switch (*p) {
160 case 'n':
161 *q = '\n';
162 break;
163 case '\\':
164 *q = '\\';
165 break;
168 *q = *p;
170 return res;
173 /* Loads the tree store from the specified filename */
174 static int
175 tree_store_load_from(char *name)
177 FILE *file;
178 char buffer[MC_MAXPATHLEN + 20], oldname[MC_MAXPATHLEN];
179 char *different;
180 int len, common;
181 int do_load;
183 g_return_val_if_fail(name != NULL, FALSE);
185 if (ts.loaded)
186 return TRUE;
188 file = fopen(name, "r");
190 if (file) {
191 fgets(buffer, sizeof(buffer), file);
193 if (strncmp(buffer, TREE_SIGNATURE, strlen(TREE_SIGNATURE)) != 0) {
194 fclose(file);
195 do_load = FALSE;
196 } else
197 do_load = TRUE;
198 } else
199 do_load = FALSE;
201 if (do_load) {
202 ts.loaded = TRUE;
204 /* File open -> read contents */
205 oldname[0] = 0;
206 while (fgets(buffer, MC_MAXPATHLEN, file)) {
207 tree_entry *e;
208 int scanned;
209 char *name;
211 /* Skip invalid records */
212 if ((buffer[0] != '0' && buffer[0] != '1'))
213 continue;
215 if (buffer[1] != ':')
216 continue;
218 scanned = buffer[0] == '1';
220 name = decode(buffer + 2);
222 len = strlen(name);
223 if (name[0] != PATH_SEP) {
224 /* Clear-text decompression */
225 char *s = strtok(name, " ");
227 if (s) {
228 common = atoi(s);
229 different = strtok(NULL, "");
230 if (different) {
231 strcpy(oldname + common, different);
232 if (vfs_file_is_local(oldname)) {
233 e = tree_store_add_entry(oldname);
234 e->scanned = scanned;
238 } else {
239 if (vfs_file_is_local(name)) {
240 e = tree_store_add_entry(name);
241 e->scanned = scanned;
243 strcpy(oldname, name);
245 g_free(name);
247 fclose(file);
250 /* Nothing loaded, we add some standard directories */
251 if (!ts.tree_first) {
252 tree_store_add_entry(PATH_SEP_STR);
253 tree_store_rescan(PATH_SEP_STR);
254 ts.loaded = TRUE;
257 return TRUE;
261 * \fn int tree_store_load(void)
262 * \brief Loads the tree from the default location
263 * \return 1 if success (true), 0 otherwise (false)
266 tree_store_load(void)
268 char *name;
269 int retval;
271 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
272 retval = tree_store_load_from(name);
273 g_free(name);
275 return retval;
278 static char *
279 encode(const char *string)
281 int special_chars;
282 const char *p;
283 char *q;
284 char *res;
286 for (special_chars = 0, p = string; *p; p++) {
287 if (*p == '\n' || *p == '\\')
288 special_chars++;
291 res = g_malloc(p - string + special_chars + 1);
292 for (p = string, q = res; *p; p++, q++) {
293 if (*p != '\n' && *p != '\\') {
294 *q = *p;
295 continue;
298 *q++ = '\\';
300 switch (*p) {
301 case '\n':
302 *q = 'n';
303 break;
305 case '\\':
306 *q = '\\';
307 break;
310 *q = 0;
311 return res;
314 /* Saves the tree to the specified filename */
315 static int
316 tree_store_save_to(char *name)
318 tree_entry *current;
319 FILE *file;
321 file = fopen(name, "w");
322 if (!file)
323 return errno;
325 fprintf(file, "%s\n", TREE_SIGNATURE);
327 current = ts.tree_first;
328 while (current) {
329 int i, common;
331 if (vfs_file_is_local(current->name)) {
332 /* Clear-text compression */
333 if (current->prev
334 && (common =
335 str_common(current->prev->name, current->name)) > 2) {
336 char *encoded = encode(current->name + common);
338 i = fprintf(file, "%d:%d %s\n", current->scanned, common,
339 encoded);
340 g_free(encoded);
341 } else {
342 char *encoded = encode(current->name);
344 i = fprintf(file, "%d:%s\n", current->scanned, encoded);
345 g_free(encoded);
348 if (i == EOF) {
349 fprintf(stderr, _("Cannot write to the %s file:\n%s\n"),
350 name, unix_error_string(errno));
351 break;
354 current = current->next;
356 tree_store_dirty(FALSE);
357 fclose(file);
359 return 0;
363 * \fn int tree_store_save(void)
364 * \brief Saves the tree to the default file in an atomic fashion
365 * \return 0 if success, errno on error
368 tree_store_save(void)
370 char *name;
371 int retval;
373 name = g_build_filename (home_dir, MC_USERCONF_DIR, MC_TREESTORE_FILE, NULL);
374 mc_util_make_backup_if_possible (name, ".tmp");
376 if ((retval = tree_store_save_to(name)) != 0) {
377 mc_util_restore_from_backup_if_possible (name, ".tmp");
378 g_free(name);
379 return retval;
382 mc_util_unlink_backup_if_possible (name, ".tmp");
383 g_free(name);
384 return 0;
387 static tree_entry *
388 tree_store_add_entry(const char *name)
390 int flag = -1;
391 tree_entry *current = ts.tree_first;
392 tree_entry *old = NULL;
393 tree_entry *new;
394 int i, len;
395 int submask = 0;
397 if (ts.tree_last && ts.tree_last->next)
398 abort();
400 /* Search for the correct place */
401 while (current && (flag = pathcmp(current->name, name)) < 0) {
402 old = current;
403 current = current->next;
406 if (flag == 0)
407 return current; /* Already in the list */
409 /* Not in the list -> add it */
410 new = g_new0(tree_entry, 1);
411 if (!current) {
412 /* Append to the end of the list */
413 if (!ts.tree_first) {
414 /* Empty list */
415 ts.tree_first = new;
416 new->prev = NULL;
417 } else {
418 old->next = new;
419 new->prev = old;
421 new->next = NULL;
422 ts.tree_last = new;
423 } else {
424 /* Insert in to the middle of the list */
425 new->prev = old;
426 if (old) {
427 /* Yes, in the middle */
428 new->next = old->next;
429 old->next = new;
430 } else {
431 /* Nope, in the beginning of the list */
432 new->next = ts.tree_first;
433 ts.tree_first = new;
435 new->next->prev = new;
438 /* Calculate attributes */
439 new->name = g_strdup(name);
440 len = strlen(new->name);
441 new->sublevel = 0;
442 for (i = 0; i < len; i++)
443 if (new->name[i] == PATH_SEP) {
444 new->sublevel++;
445 new->subname = new->name + i + 1;
447 if (new->next)
448 submask = new->next->submask;
449 else
450 submask = 0;
451 submask |= 1 << new->sublevel;
452 submask &= (2 << new->sublevel) - 1;
453 new->submask = submask;
454 new->mark = 0;
456 /* Correct the submasks of the previous entries */
457 current = new->prev;
458 while (current && current->sublevel > new->sublevel) {
459 current->submask |= 1 << new->sublevel;
460 current = current->prev;
463 /* The entry has now been added */
465 if (new->sublevel > 1) {
466 /* Let's check if the parent directory is in the tree */
467 char *parent = g_strdup(new->name);
468 int i;
470 for (i = strlen(parent) - 1; i > 1; i--) {
471 if (parent[i] == PATH_SEP) {
472 parent[i] = 0;
473 tree_store_add_entry(parent);
474 break;
477 g_free(parent);
480 tree_store_dirty(TRUE);
481 return new;
484 static Hook *remove_entry_hooks;
486 void
487 tree_store_add_entry_remove_hook(tree_store_remove_fn callback, void *data)
489 add_hook(&remove_entry_hooks, (void (*)(void *)) callback, data);
492 void
493 tree_store_remove_entry_remove_hook(tree_store_remove_fn callback)
495 delete_hook(&remove_entry_hooks, (void (*)(void *)) callback);
498 static void
499 tree_store_notify_remove(tree_entry * entry)
501 Hook *p = remove_entry_hooks;
502 tree_store_remove_fn r;
504 while (p) {
505 r = (tree_store_remove_fn) p->hook_fn;
506 r(entry, p->hook_data);
507 p = p->next;
511 static tree_entry *
512 remove_entry(tree_entry * entry)
514 tree_entry *current = entry->prev;
515 long submask = 0;
516 tree_entry *ret = NULL;
518 tree_store_notify_remove(entry);
520 /* Correct the submasks of the previous entries */
521 if (entry->next)
522 submask = entry->next->submask;
523 while (current && current->sublevel > entry->sublevel) {
524 submask |= 1 << current->sublevel;
525 submask &= (2 << current->sublevel) - 1;
526 current->submask = submask;
527 current = current->prev;
530 /* Unlink the entry from the list */
531 if (entry->prev)
532 entry->prev->next = entry->next;
533 else
534 ts.tree_first = entry->next;
536 if (entry->next)
537 entry->next->prev = entry->prev;
538 else
539 ts.tree_last = entry->prev;
541 /* Free the memory used by the entry */
542 g_free(entry->name);
543 g_free(entry);
545 return ret;
548 void
549 tree_store_remove_entry(const char *name)
551 tree_entry *current, *base, *old;
552 int len;
554 g_return_if_fail(name != NULL);
556 /* Miguel Ugly hack */
557 if (name[0] == PATH_SEP && name[1] == 0)
558 return;
559 /* Miguel Ugly hack end */
561 base = tree_store_whereis(name);
562 if (!base)
563 return; /* Doesn't exist */
565 len = strlen(base->name);
566 current = base->next;
567 while (current
568 && strncmp(current->name, base->name, len) == 0
569 && (current->name[len] == '\0'
570 || current->name[len] == PATH_SEP)) {
571 old = current;
572 current = current->next;
573 remove_entry(old);
575 remove_entry(base);
576 tree_store_dirty(TRUE);
578 return;
581 /* This subdirectory exists -> clear deletion mark */
582 void
583 tree_store_mark_checked(const char *subname)
585 char *name;
586 tree_entry *current, *base;
587 int flag = 1, len;
588 if (!ts.loaded)
589 return;
591 if (ts.check_name == NULL)
592 return;
594 /* Calculate the full name of the subdirectory */
595 if (subname[0] == '.' &&
596 (subname[1] == 0 || (subname[1] == '.' && subname[2] == 0)))
597 return;
598 if (ts.check_name[0] == PATH_SEP && ts.check_name[1] == 0)
599 name = g_strconcat(PATH_SEP_STR, subname, (char *) NULL);
600 else
601 name = concat_dir_and_file(ts.check_name, subname);
603 /* Search for the subdirectory */
604 current = ts.check_start;
605 while (current && (flag = pathcmp(current->name, name)) < 0)
606 current = current->next;
608 if (flag != 0) {
609 /* Doesn't exist -> add it */
610 current = tree_store_add_entry(name);
611 ts.add_queue = g_list_prepend(ts.add_queue, g_strdup(name));
613 g_free(name);
615 /* Clear the deletion mark from the subdirectory and its children */
616 base = current;
617 if (base) {
618 len = strlen(base->name);
619 base->mark = 0;
620 current = base->next;
621 while (current
622 && strncmp(current->name, base->name, len) == 0
623 && (current->name[len] == '\0'
624 || current->name[len] == PATH_SEP || len == 1)) {
625 current->mark = 0;
626 current = current->next;
631 /* Mark the subdirectories of the current directory for delete */
632 tree_entry *
633 tree_store_start_check(const char *path)
635 tree_entry *current, *retval;
636 int len;
638 if (!ts.loaded)
639 return NULL;
641 g_return_val_if_fail(ts.check_name == NULL, NULL);
642 ts.check_start = NULL;
644 /* Search for the start of subdirectories */
645 current = tree_store_whereis(path);
646 if (!current) {
647 struct stat s;
649 if (mc_stat(path, &s) == -1)
650 return NULL;
652 if (!S_ISDIR(s.st_mode))
653 return NULL;
655 current = tree_store_add_entry(path);
656 ts.check_name = g_strdup(path);
658 return current;
661 ts.check_name = g_strdup(path);
663 retval = current;
665 /* Mark old subdirectories for delete */
666 ts.check_start = current->next;
667 len = strlen(ts.check_name);
669 current = ts.check_start;
670 while (current
671 && strncmp(current->name, ts.check_name, len) == 0
672 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
673 || len == 1)) {
674 current->mark = 1;
675 current = current->next;
678 return retval;
681 /* Delete subdirectories which still have the deletion mark */
682 void
683 tree_store_end_check(void)
685 tree_entry *current, *old;
686 int len;
687 GList *the_queue, *l;
689 if (!ts.loaded)
690 return;
692 g_return_if_fail(ts.check_name != NULL);
694 /* Check delete marks and delete if found */
695 len = strlen(ts.check_name);
697 current = ts.check_start;
698 while (current
699 && strncmp(current->name, ts.check_name, len) == 0
700 && (current->name[len] == '\0' || current->name[len] == PATH_SEP
701 || len == 1)) {
702 old = current;
703 current = current->next;
704 if (old->mark)
705 remove_entry(old);
708 /* get the stuff in the scan order */
709 ts.add_queue = g_list_reverse(ts.add_queue);
710 the_queue = ts.add_queue;
711 ts.add_queue = NULL;
712 g_free(ts.check_name);
713 ts.check_name = NULL;
715 for (l = the_queue; l; l = l->next) {
716 g_free(l->data);
719 g_list_free(the_queue);
722 static void
723 process_special_dirs(GList ** special_dirs, char *file)
725 gchar **buffers, **start_buff;
726 mc_config_t *cfg;
727 gsize buffers_len;
729 cfg = mc_config_init(file);
730 if (cfg == NULL)
731 return;
733 start_buff = buffers = mc_config_get_string_list(cfg, "Special dirs", "list", &buffers_len);
734 if (buffers == NULL){
735 mc_config_deinit(cfg);
736 return;
739 while(*buffers) {
740 *special_dirs = g_list_prepend(*special_dirs, g_strdup(*buffers));
741 buffers++;
743 g_strfreev(start_buff);
744 mc_config_deinit(cfg);
747 static gboolean
748 should_skip_directory(const char *dir)
750 static GList *special_dirs;
751 GList *l;
752 static int loaded;
754 if (loaded == 0) {
755 loaded = 1;
756 setup_init();
757 process_special_dirs(&special_dirs, profile_name);
758 process_special_dirs(&special_dirs, global_profile_name);
761 for (l = special_dirs; l; l = l->next) {
762 if (strncmp(dir, l->data, strlen(l->data)) == 0)
763 return TRUE;
765 return FALSE;
768 tree_entry *
769 tree_store_rescan(const char *dir)
771 DIR *dirp;
772 struct dirent *dp;
773 struct stat buf;
774 tree_entry *entry;
776 if (should_skip_directory(dir)) {
777 entry = tree_store_add_entry(dir);
778 entry->scanned = 1;
780 return entry;
783 entry = tree_store_start_check(dir);
785 if (!entry)
786 return NULL;
788 dirp = mc_opendir(dir);
789 if (dirp) {
790 for (dp = mc_readdir(dirp); dp; dp = mc_readdir(dirp)) {
791 char *full_name;
793 if (dp->d_name[0] == '.') {
794 if (dp->d_name[1] == 0
795 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))
796 continue;
799 full_name = concat_dir_and_file(dir, dp->d_name);
800 if (mc_lstat(full_name, &buf) != -1) {
801 if (S_ISDIR(buf.st_mode))
802 tree_store_mark_checked(dp->d_name);
804 g_free(full_name);
806 mc_closedir(dirp);
808 tree_store_end_check();
809 entry->scanned = 1;
811 return entry;