dir-project: Decrease project load priority to avoid blocking GUI
[anjuta.git] / plugins / dir-project / dir-project.c
blob23122d7afb178432b29289c061119b1e5e56d15a
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4; coding: utf-8 -*- */
2 /* dir-project.c
4 * Copyright (C) 2009 Sébastien Granjoux
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
27 #include "dir-project.h"
29 #include "dir-node.h"
31 #include <libanjuta/interfaces/ianjuta-project.h>
32 #include <libanjuta/anjuta-debug.h>
33 #include <libanjuta/anjuta-utils.h>
35 #include <string.h>
36 #include <memory.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <unistd.h>
40 #include <ctype.h>
41 #include <sys/types.h>
42 #include <signal.h>
43 #include <glib/gi18n.h>
44 #include <gio/gio.h>
45 #include <glib.h>
47 #define SOURCES_FILE PACKAGE_DATA_DIR "/sources.list"
49 struct _DirProject {
50 AnjutaDirRootNode parent;
52 /* shortcut hash tables, mapping id -> GNode from the tree above */
53 GHashTable *groups;
55 /* project files monitors */
56 GHashTable *monitors;
58 /* List of source files pattern */
59 GList *sources;
62 struct _DirProjectClass {
63 AnjutaDirRootNodeClass parent_class;
66 /* A file or directory name part of a path */
67 typedef struct _DirMatchString DirMatchString;
69 struct _DirMatchString
71 gchar *string;
72 gchar *reverse;
73 guint length;
74 GFile *file;
75 gboolean parent;
79 /* A pattern used to match a part of a path */
80 typedef struct _DirPattern DirPattern;
82 struct _DirPattern
84 gboolean match;
85 gboolean directory;
86 gchar *object;
87 GRegex *regex;
90 /* A list of pattern found in one file */
91 typedef struct _DirPatternList DirPatternList;
93 struct _DirPatternList
95 GList *sources;
96 GList *objects;
97 GFile *directory;
98 GHashTable *extensions;
101 /* ----- Standard GObject types and variables ----- */
103 enum {
104 PROP_0,
105 PROP_PROJECT_DIR
108 static GObject *parent_class;
110 /* Helper functions
111 *---------------------------------------------------------------------------*/
113 static AnjutaProjectNode *
114 project_node_new (DirProject *project, AnjutaProjectNode *parent, AnjutaProjectNodeType type, GFile *file, const gchar *name, GError **error)
116 AnjutaProjectNode *node = NULL;
118 switch (type & ANJUTA_PROJECT_TYPE_MASK) {
119 case ANJUTA_PROJECT_GROUP:
120 if (file == NULL)
122 if (name == NULL)
124 g_set_error (error, IANJUTA_PROJECT_ERROR,
125 IANJUTA_PROJECT_ERROR_VALIDATION_FAILED,
126 _("Missing name"));
128 else
130 GFile *group_file;
132 group_file = g_file_get_child (anjuta_project_node_get_file (parent), name);
133 node = dir_group_node_new (group_file, G_OBJECT (project));
134 g_object_unref (group_file);
137 else
139 node = dir_group_node_new (file, G_OBJECT (project));
141 break;
142 case ANJUTA_PROJECT_OBJECT:
143 if (file == NULL)
145 if (name == NULL)
147 g_set_error (error, IANJUTA_PROJECT_ERROR,
148 IANJUTA_PROJECT_ERROR_VALIDATION_FAILED,
149 _("Missing name"));
151 else
153 GFile *object_file;
155 object_file = g_file_get_child (anjuta_project_node_get_file (parent), name);
156 node = dir_object_node_new (object_file);
157 g_object_unref (object_file);
160 else
162 node = dir_object_node_new (file);
164 break;
165 case ANJUTA_PROJECT_SOURCE:
166 if (file == NULL)
168 if (name == NULL)
170 g_set_error (error, IANJUTA_PROJECT_ERROR,
171 IANJUTA_PROJECT_ERROR_VALIDATION_FAILED,
172 _("Missing name"));
174 else
176 GFile *source_file;
178 source_file = g_file_get_child (anjuta_project_node_get_file (parent), name);
179 node = dir_source_node_new (source_file);
180 g_object_unref (source_file);
183 else
185 node = dir_source_node_new (file);
187 break;
188 default:
189 g_assert_not_reached ();
190 break;
192 if (node != NULL)
194 node->type = type;
195 node->parent = parent;
198 return node;
202 /* Pattern objects
203 *---------------------------------------------------------------------------*/
205 static void
206 dir_pattern_free (DirPattern *pat)
208 g_free (pat->object);
209 if (pat->regex != NULL) g_regex_unref (pat->regex);
211 g_slice_free (DirPattern, pat);
214 /* Create a new pattern matching a directory of a file name in a path */
216 static DirPattern*
217 dir_pattern_new (const gchar *pattern, gboolean reverse)
219 DirPattern *pat = NULL;
220 GString *regex = g_string_new (NULL);
221 const char *ptr = pattern;
223 pat = g_slice_new0(DirPattern);
224 /* Check if it is a reverse pattern */
225 if (*ptr == '!')
227 pat->match = reverse ? TRUE : FALSE;
228 ptr++;
230 else
232 pat->match = reverse ? FALSE : TRUE;
235 /* Check if the pattern is local */
236 if (*ptr == '/')
238 g_string_append_c (regex, '^');
239 ptr++;
241 else
243 g_string_append (regex, "(?:^|\\" G_DIR_SEPARATOR_S ")");
247 while (*ptr != '\0')
249 const gchar *next;
251 next = ptr + strcspn (ptr, "\\:.^$[|()?*+{");
253 g_string_append_len (regex, ptr, next - ptr);
254 ptr = next;
255 if (*ptr == ':')
257 /* Remaining data are for object */
258 break;
260 else if (*ptr == '*')
262 /* Replace with corresponding regular expression */
263 g_string_append (regex, "(.+)");
264 ptr++;
266 else if (*ptr == '?')
268 /* Replace with corresponding regular expression */
271 ptr++;
272 } while (*ptr == '?');
273 g_string_append_printf (regex, "(.{%ld})", (long int) (ptr - next));
275 else if (*ptr == '\\')
277 /* Add next character without a special signification */
278 g_string_append_c (regex, *ptr++);
279 if (*ptr != '\0') g_string_append_c (regex, *ptr++);
281 else if (isspace (*ptr))
283 /* Skip space */
284 continue;
286 else if (*ptr != '\0')
288 /* Automatically escape character if not done */
289 if ((ptr == pattern) || (*(ptr - 1) != '\\'))
291 g_string_append_c (regex, '\\');
293 g_string_append_c (regex, *ptr++);
296 if ((regex->len > 1) && (regex->str[regex->len - 1] == '/'))
298 /* Match directory only */
299 pat->directory = TRUE;
300 g_string_truncate (regex, regex->len - 1);
302 g_string_append_c (regex, '$');
303 pat->regex = g_regex_new (regex->str, G_REGEX_OPTIMIZE, 0, NULL);
304 if (pat->regex == NULL)
306 dir_pattern_free (pat);
307 pat = NULL;
310 if ((pat != NULL) && (*ptr == ':'))
312 g_string_truncate (regex, 0);
314 ptr++;
315 while (isspace (*ptr)) ptr++;
317 while (*ptr != '\0')
319 const gchar *next;
320 gint replace = 1;
322 next = ptr + strcspn (ptr, "\\?*");
324 g_string_append_len (regex, ptr, next - ptr);
325 ptr = next;
326 if (*ptr == '*')
328 /* Replace with corresponding replacement */
329 g_string_append_printf (regex, "\\%d", replace++);
330 ptr++;
332 else if (*ptr == '?')
336 ptr++;
337 } while (*ptr == '?');
338 g_string_append_printf (regex, "\\%d", replace++);
340 else if (*ptr == '\\')
342 /* Add next character without a special signification */
343 g_string_append_c (regex, *ptr++);
344 if (*ptr != '\0') g_string_append_c (regex, *ptr++);
347 pat->object = g_string_free (regex, FALSE);
349 else
351 g_string_free (regex, TRUE);
354 return pat;
357 /* Replace regular expression by a lookup in a hash table if we look only for
358 * a file with a particular extension as it's much faster.
359 * Return TRUE if it is possible */
361 static gboolean
362 dir_pattern_optimize (DirPattern *pat, DirPattern *last, GHashTable *extensions)
364 const gchar *pattern = g_regex_get_pattern (pat->regex);
365 const gchar *ext;
367 ext = strrchr (pattern, '.');
368 if ((ext != NULL) &&
369 (strncmp (pattern, "(?:^|\\/)(.+)\\", ext - pattern) == 0))
371 const gchar *ptr;
373 for (ptr = ext + 1; isalnum(*ptr) || (*ptr == '_') || ((ptr[0] == '\\') && (ptr[1] == '+')); *ptr == '\\' ? ptr += 2 : ptr++);
374 if ((ptr[0] == '$') && (ptr[1] == '\0'))
376 gchar *key = g_strndup (ext + 1, strlen(ext) - 2);
377 if (g_hash_table_lookup (extensions, key) == NULL)
379 g_hash_table_insert (extensions, key, last == NULL ? pat : last);
381 return TRUE;
386 return FALSE;
389 /* Read a file containing pattern, the syntax is similar to .gitignore file.
391 * It is not a regular expression, only * and ? are used as joker.
392 * If the name end with / it will match only a directory.
393 * If the name starts with / it must be relative to the project directory, so
394 * by example /.git/ will match only a directory named .git in the project
395 * directory, while CVS/ will match a directory named CVS anywhere in the
396 * project.
397 * If the name starts with ! the meaning is reversed. In a file containing
398 * matching file, if a pattern starting ! matches, it means that the file has
399 * to be removed from the matching list.
400 * All pattern are read in order, so it is possible to match a group of files
401 * and add pattern afterward to remove some of these files.
402 * A name starting with # is a comment.
403 * All spaces at the beginning of a name are ignored.
404 * If a name is followed by : a following name is taken as the object file name
406 static GList*
407 dir_push_pattern_list (GList *stack, GFile *dir, GFile *file, gboolean ignore, GError **error)
409 char *content;
410 char *ptr;
411 DirPatternList *list = NULL;
412 guint line;
413 DirPattern *last = NULL;
415 if (!g_file_load_contents (file, NULL, &content, NULL, NULL, error))
417 return stack;
420 list = g_slice_new0(DirPatternList);
421 list->sources = NULL;
422 list->objects = NULL;
423 list->directory = dir;
424 list->extensions = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
426 line = 1;
427 for (ptr = content; *ptr != '\0';)
429 gchar *next;
431 next = strchr (ptr, '\n');
432 if (next != NULL) *next = '\0';
433 line++;
435 /* Discard space at the beginning */
436 while (isspace (*ptr)) ptr++;
438 if ((*ptr != '#') && (ptr != next))
440 /* Create pattern */
441 DirPattern *pat = NULL;
442 gboolean used = FALSE;
444 if (next != NULL) *next = '\0';
445 pat = dir_pattern_new (ptr, ignore);
446 if (pat != NULL)
448 if ((last != NULL) && (last->match != pat->match)) last = NULL;
449 if (dir_pattern_optimize (pat, last, list->extensions))
451 if (last == NULL)
453 last = pat;
454 g_regex_unref (pat->regex);
455 pat->regex = NULL;
456 list->sources = g_list_prepend (list->sources, pat);
457 used = TRUE;
460 else
462 list->sources = g_list_prepend (list->sources, pat);
463 last = NULL;
464 used = TRUE;
467 if (pat->object != NULL)
469 if (used) pat = dir_pattern_new (ptr, ignore);
470 list->objects = g_list_prepend (list->objects, pat);
472 else if (!used)
474 dir_pattern_free (pat);
477 else
479 gchar *filename = g_file_get_path (file);
480 g_warning("Invalid pattern in %s line %d", filename, line);
481 g_free (filename);
485 if (next == NULL) break;
486 ptr = next + 1;
488 g_free (content);
490 list->sources = g_list_reverse (list->sources);
491 list->objects = g_list_reverse (list->objects);
493 return g_list_prepend (stack, list);
496 static GList *
497 dir_pop_pattern_list (GList *stack)
499 DirPatternList *top = (DirPatternList *)stack->data;
501 stack = g_list_remove_link (stack, stack);
503 g_list_foreach (top->sources, (GFunc)dir_pattern_free, NULL);
504 g_list_free (top->sources);
505 g_list_foreach (top->objects, (GFunc)dir_pattern_free, NULL);
506 g_list_free (top->objects);
507 g_object_unref (top->directory);
508 g_hash_table_destroy (top->extensions);
509 g_slice_free (DirPatternList, top);
511 return stack;
514 static gboolean
515 dir_pattern_stack_is_match (GFile *root, GList *stack, GFile *file)
517 gboolean match;
518 GList *list;
519 gchar *filename;
520 gboolean directory;
522 /* Create name from file */
523 filename = g_file_get_relative_path (root, file);
525 directory = g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_DIRECTORY;
526 /* Include directories by default */
527 match = directory;
529 /* Check all valid patterns */
530 for (list = g_list_last (stack); list != NULL; list = g_list_previous (list))
532 DirPatternList *pat_list = (DirPatternList *)list->data;
533 GList *node;
534 DirPattern *pat_ext = NULL;
535 const gchar *ext;
537 /* Check only the extension to be faster on the common case */
538 ext = strrchr (filename, '.');
539 if (ext != NULL)
541 pat_ext = g_hash_table_lookup (pat_list->extensions, ext + 1);
544 for (node = g_list_first (pat_list->sources); node != NULL; node = g_list_next (node))
546 DirPattern *pat = (DirPattern *)node->data;
548 if ((pat->directory && !directory) || (!pat->directory && directory))
549 continue;
551 if ((pat == pat_ext) || ((pat->regex != NULL) && g_regex_match (pat->regex, filename, 0, NULL)))
553 match = pat->match;
557 g_free (filename);
559 return match;
562 static GFile *
563 dir_pattern_find_file_object (GFile *root, GList *stack, GFile *file)
565 GFile *object = NULL;
567 if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) != G_FILE_TYPE_DIRECTORY)
569 GList *list;
570 gchar *filename;
572 /* Create name from file */
573 filename = g_file_get_relative_path (root, file);
575 /* Check all valid patterns */
576 for (list = g_list_last (stack); list != NULL; list = g_list_previous (list))
578 DirPatternList *pat_list = (DirPatternList *)list->data;
579 GList *node;
581 for (node = g_list_first (pat_list->objects); node != NULL; node = g_list_next (node))
583 DirPattern *pat = (DirPattern *)node->data;
585 if (pat->directory || !pat->match || (pat->object == NULL) )
586 continue;
588 if (g_regex_match (pat->regex, filename, 0, NULL))
590 gchar *objname;
592 objname = g_regex_replace (pat->regex, filename, -1, 0, pat->object, 0, NULL);
593 object = g_file_get_child (root, objname);
594 g_free (objname);
598 g_free (filename);
601 return object;
605 typedef struct {
606 DirProject *proj;
607 AnjutaProjectNode *parent;
608 } DirData;
610 /* the number of files to enumerate each time */
611 #define NUM_FILES 10
613 static void
614 dir_project_load_directory_callback (GObject *source_object,
615 GAsyncResult *res,
616 gpointer user_data)
618 GFileEnumerator *enumerator = G_FILE_ENUMERATOR(source_object);
619 GList *infos, *l;
620 GError *err = NULL;
621 DirData *data = (DirData *) user_data;
622 GFile *root;
624 infos = g_file_enumerator_next_files_finish (enumerator, res, &err);
625 if (infos == NULL) {
626 GList *removed = NULL;
628 /* either we are finished or an error occured */
629 anjuta_project_node_clear_state (data->parent, ANJUTA_PROJECT_LOADING);
630 if (err != NULL) {
631 g_signal_emit_by_name (data->proj, "node-loaded", data->parent, err);
632 g_error_free (err);
633 } else {
634 AnjutaProjectNode *node, *next;
635 for (node = anjuta_project_node_first_child (data->parent);
636 node != NULL;
637 node = next)
639 int state = anjuta_project_node_get_state (node);
641 next = anjuta_project_node_next_sibling (node);
642 if (state & ANJUTA_PROJECT_LOADING)
644 /* these nodes are no longer necessary */
645 gchar *uri = g_file_get_uri (node->file);
646 g_hash_table_remove (data->proj->groups, uri);
647 g_free (uri);
649 anjuta_project_node_remove (node);
650 removed = g_list_prepend (removed, node);
653 g_signal_emit_by_name (data->proj, "node-loaded", data->parent, NULL);
655 g_list_foreach (removed, (GFunc)g_object_unref, NULL);
656 g_list_free (removed);
657 g_object_unref (data->parent);
658 g_slice_free (DirData, data);
659 g_object_unref (enumerator);
661 return;
664 root = anjuta_project_node_get_file (ANJUTA_PROJECT_NODE (data->proj));
665 for (l = infos; l != NULL; l = g_list_next(l))
667 GFileInfo *info;
668 const gchar *name;
669 GFile *file;
671 info = G_FILE_INFO(l->data);
673 name = g_file_info_get_name (info);
674 file = g_file_get_child (data->parent->file, name);
675 g_object_unref (info);
677 /* Check if file is a source */
678 if (!dir_pattern_stack_is_match (root, data->proj->sources, file)) continue;
680 if (g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL) == G_FILE_TYPE_DIRECTORY)
682 AnjutaProjectNode *group;
683 gchar *uri;
685 uri = g_file_get_uri (file);
686 group = g_hash_table_lookup (data->proj->groups, uri);
687 if (group != NULL)
689 anjuta_project_node_clear_state (group, ANJUTA_PROJECT_LOADING);
690 g_free (uri);
692 else
694 /* Create a group for directory */
695 group = project_node_new (data->proj, NULL, ANJUTA_PROJECT_GROUP, file, NULL, NULL);
696 g_hash_table_insert (data->proj->groups, uri, group);
697 anjuta_project_node_append (data->parent, group);
698 anjuta_project_node_set_state (group, ANJUTA_PROJECT_INCOMPLETE);
701 else
703 AnjutaProjectNode *source = NULL;
705 AnjutaProjectNode *node;
706 for (node = anjuta_project_node_first_child (data->parent);
707 node != NULL;
708 node = anjuta_project_node_next_sibling (node))
710 source = (anjuta_project_node_get_node_type (node) == ANJUTA_PROJECT_OBJECT) ?
711 anjuta_project_node_first_child (node) :
712 node;
713 if (g_file_equal (file, anjuta_project_node_get_file (source)))
715 anjuta_project_node_clear_state (node, ANJUTA_PROJECT_LOADING);
716 break;
718 source = NULL;
721 if (source == NULL)
723 GFile *object;
724 AnjutaProjectNode *parent;
726 /* Create object if possible */
727 object = dir_pattern_find_file_object (root, data->proj->sources, file);
728 if (object != NULL)
730 parent = project_node_new (data->proj, NULL, ANJUTA_PROJECT_OBJECT | ANJUTA_PROJECT_PROJECT, object, NULL, NULL);
731 g_object_unref (object);
732 anjuta_project_node_append (data->parent, parent);
734 else
736 parent = data->parent;
739 /* Create a source for files */
740 source = project_node_new (data->proj, NULL, ANJUTA_PROJECT_SOURCE | ANJUTA_PROJECT_PROJECT, file, NULL, NULL);
741 anjuta_project_node_append (parent, source);
745 g_list_free (infos);
747 g_file_enumerator_next_files_async (enumerator, NUM_FILES, G_PRIORITY_DEFAULT_IDLE, NULL,
748 dir_project_load_directory_callback, data);
751 /* Public functions
752 *---------------------------------------------------------------------------*/
754 static AnjutaProjectNode *
755 dir_project_load_directory (DirProject *project, AnjutaProjectNode *parent, GError **error)
757 GFileEnumerator *enumerator;
759 enumerator = g_file_enumerate_children (parent->file,
760 G_FILE_ATTRIBUTE_STANDARD_NAME,
761 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
762 NULL,
763 error);
765 if (enumerator == NULL)
766 return parent;
768 /* mark all children as loading so we can remove them if no longer relevant */
769 AnjutaProjectNode *node;
770 for (node = anjuta_project_node_first_child (parent);
771 node != NULL;
772 node = anjuta_project_node_next_sibling (node))
774 anjuta_project_node_set_state (node, ANJUTA_PROJECT_LOADING);
777 DirData *data = g_slice_new (DirData);
778 data->proj = project;
779 data->parent = g_object_ref (parent);
781 g_file_enumerator_next_files_async (enumerator, NUM_FILES, G_PRIORITY_DEFAULT_IDLE, NULL,
782 dir_project_load_directory_callback, data);
784 anjuta_project_node_set_state (parent, ANJUTA_PROJECT_LOADING);
785 return parent;
788 static AnjutaProjectNode *
789 dir_project_load_root (DirProject *project, GError **error)
791 GFile *source_file;
792 GFile *root_file;
794 root_file = anjuta_project_node_get_file (ANJUTA_PROJECT_NODE (project));
795 DEBUG_PRINT ("reload project %p root file %p", project, root_file);
797 if (g_file_query_file_type (root_file, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY)
799 g_set_error (error, IANJUTA_PROJECT_ERROR,
800 IANJUTA_PROJECT_ERROR_DOESNT_EXIST,
801 _("Project doesn't exist or invalid path"));
803 return NULL;
806 /* shortcut hash tables */
807 if (project->groups == NULL)
809 project->groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
812 /* Load source pattern */
813 source_file = g_file_new_for_path (SOURCES_FILE);
814 project->sources = dir_push_pattern_list (NULL, g_object_ref (root_file), source_file, FALSE, NULL);
815 g_object_unref (source_file);
817 dir_group_node_set_file (ANJUTA_DIR_GROUP_NODE (project), root_file, G_OBJECT (project));
818 dir_project_load_directory (project, ANJUTA_PROJECT_NODE (project), NULL);
820 return ANJUTA_PROJECT_NODE (project);
823 AnjutaProjectNode *
824 dir_project_load_node (DirProject *project, AnjutaProjectNode *node, GError **error)
826 if (node == NULL) node = ANJUTA_PROJECT_NODE (project);
827 switch (anjuta_project_node_get_node_type (node))
829 case ANJUTA_PROJECT_ROOT:
830 return dir_project_load_root (project, error);
831 case ANJUTA_PROJECT_GROUP:
832 return dir_project_load_directory (project, node, error);
833 default:
834 return NULL;
838 static void
839 foreach_node_save (AnjutaProjectNode *node,
840 gpointer data)
842 gint state = anjuta_project_node_get_state (node);
843 AnjutaProjectNode *parent;
844 GError *err = NULL;
847 if (state & ANJUTA_PROJECT_MODIFIED)
849 switch (anjuta_project_node_get_node_type (node))
851 case ANJUTA_PROJECT_GROUP:
852 g_file_make_directory_with_parents (anjuta_project_node_get_file (node), NULL, NULL);
853 break;
854 case ANJUTA_PROJECT_SOURCE:
855 for (parent = node;
856 (parent != NULL) && (anjuta_project_node_get_node_type (parent) != ANJUTA_PROJECT_GROUP) && (anjuta_project_node_get_node_type (parent) != ANJUTA_PROJECT_ROOT);
857 parent = anjuta_project_node_parent (parent));
858 if (parent != NULL)
860 GFile *file = anjuta_project_node_get_file (node);
861 gchar *basename = g_file_get_basename (file);
862 GFile *dest = g_file_get_child (anjuta_project_node_get_file (parent), basename);
864 g_free (basename);
865 if (!g_file_equal (dest, file))
867 g_file_copy_async (file, dest, G_FILE_COPY_BACKUP, G_PRIORITY_DEFAULT, NULL, NULL, NULL, NULL, NULL);
868 node->file = dest;
869 dest = file;
872 g_object_unref (dest);
874 break;
875 default:
876 break;
879 else if (state & ANJUTA_PROJECT_REMOVED)
881 switch (anjuta_project_node_get_node_type (node))
883 case ANJUTA_PROJECT_GROUP:
884 case ANJUTA_PROJECT_SOURCE:
885 g_file_trash (anjuta_project_node_get_file (node), NULL, &err);
886 if (err != NULL)
888 g_warning ("trashing file failed with %s", err->message);
889 g_error_free (err);
891 break;
892 default:
893 break;
897 anjuta_project_node_clear_state (node, ANJUTA_PROJECT_REMOVED | ANJUTA_PROJECT_MODIFIED);
900 AnjutaProjectNode *
901 dir_project_save_node (DirProject *project, AnjutaProjectNode *node, GError **error)
903 /* Save children */
904 anjuta_project_node_foreach (node, G_POST_ORDER, foreach_node_save, project);
906 return node;
909 void
910 dir_project_unload (DirProject *project)
912 /* shortcut hash tables */
913 if (project->groups) g_hash_table_destroy (project->groups);
914 project->groups = NULL;
916 /* sources patterns */
917 while (project->sources)
919 project->sources = dir_pop_pattern_list (project->sources);
923 gint
924 dir_project_probe (GFile *file,
925 GError **error)
927 gint probe;
929 probe = g_file_query_file_type (file, G_FILE_QUERY_INFO_NONE, NULL) == G_FILE_TYPE_DIRECTORY;
930 if (!probe)
932 g_set_error (error, IANJUTA_PROJECT_ERROR,
933 IANJUTA_PROJECT_ERROR_DOESNT_EXIST,
934 _("Project doesn't exist or invalid path"));
937 return probe ? IANJUTA_PROJECT_PROBE_FILES : 0;
940 static GList *
941 dir_project_get_node_info (DirProject *project, GError **error)
943 static AnjutaProjectNodeInfo node_info[] = {
944 {ANJUTA_PROJECT_GROUP,
945 N_("Group"),
947 NULL},
948 {ANJUTA_PROJECT_SOURCE,
949 N_("Source"),
951 NULL},
952 {ANJUTA_PROJECT_UNKNOWN,
953 NULL,
954 NULL,
955 NULL}};
956 static GList *info_list = NULL;
958 if (info_list == NULL)
960 AnjutaProjectNodeInfo *node;
962 for (node = node_info; node->type != 0; node++)
964 info_list = g_list_prepend (info_list, node);
967 info_list = g_list_reverse (info_list);
970 return info_list;
973 static gboolean
974 find_not_loaded_node (gpointer key, gpointer value, gpointer user_data)
976 AnjutaProjectNode *node = (AnjutaProjectNode *)value;
977 gboolean found;
979 found = anjuta_project_node_get_state (node) & (ANJUTA_PROJECT_LOADING | ANJUTA_PROJECT_INCOMPLETE);
981 return found;
984 static gboolean
985 dir_project_is_loaded (DirProject *project)
987 if (project->groups == NULL)
988 return FALSE;
990 return g_hash_table_find (project->groups, find_not_loaded_node, NULL) == NULL;
993 /* Public functions
994 *---------------------------------------------------------------------------*/
996 DirProject *
997 dir_project_new (GFile *directory, GError **error)
999 DirProject *project;
1001 project = DIR_PROJECT (g_object_new (DIR_TYPE_PROJECT, NULL));
1002 project->parent.base.file = g_object_ref (directory);
1004 return project;
1008 /* Implement IAnjutaProject
1009 *---------------------------------------------------------------------------*/
1011 static gboolean
1012 iproject_load_node (IAnjutaProject *obj, AnjutaProjectNode *node, GError **err)
1014 node = dir_project_load_node (DIR_PROJECT (obj), node, err);
1016 return node != NULL;
1019 static gboolean
1020 iproject_save_node (IAnjutaProject *obj, AnjutaProjectNode *node, GError **err)
1022 node = dir_project_save_node (DIR_PROJECT (obj), node, err);
1023 g_signal_emit_by_name (obj, "node-saved", node, err);
1025 return node != NULL;
1028 static AnjutaProjectNode *
1029 iproject_add_node_before (IAnjutaProject *obj, AnjutaProjectNode *parent, AnjutaProjectNode *sibling, AnjutaProjectNodeType type, GFile *file, const gchar *name, GError **error)
1031 AnjutaProjectNode *node;
1033 node = project_node_new (DIR_PROJECT (obj), parent, type, file, name, error);
1034 anjuta_project_node_set_state (node, ANJUTA_PROJECT_MODIFIED);
1035 anjuta_project_node_insert_before (parent, sibling, node);
1037 g_signal_emit_by_name (obj, "node-changed", node, NULL);
1039 return node;
1042 static AnjutaProjectNode *
1043 iproject_add_node_after (IAnjutaProject *obj, AnjutaProjectNode *parent, AnjutaProjectNode *sibling, AnjutaProjectNodeType type, GFile *file, const gchar *name, GError **error)
1045 AnjutaProjectNode *node;
1047 node = project_node_new (DIR_PROJECT (obj), parent, type, file, name, error);
1048 anjuta_project_node_set_state (node, ANJUTA_PROJECT_MODIFIED);
1049 anjuta_project_node_insert_after (parent, sibling, node);
1051 g_signal_emit_by_name (obj, "node-changed", node, NULL);
1053 return node;
1056 static gboolean
1057 iproject_remove_node (IAnjutaProject *obj, AnjutaProjectNode *node, GError **err)
1059 anjuta_project_node_set_state (node, ANJUTA_PROJECT_REMOVED);
1060 g_signal_emit_by_name (obj, "node-changed", node, NULL);
1062 return TRUE;
1065 static AnjutaProjectProperty*
1066 iproject_set_property (IAnjutaProject *obj, AnjutaProjectNode *node, const gchar *id, const gchar *name, const gchar *value, GError **error)
1068 g_set_error (error, IANJUTA_PROJECT_ERROR,
1069 IANJUTA_PROJECT_ERROR_NOT_SUPPORTED,
1070 _("Project doesn't allow to set properties"));
1072 return NULL;
1075 static gboolean
1076 iproject_remove_property (IAnjutaProject *obj, AnjutaProjectNode *node, const gchar *id, const gchar *name, GError **error)
1078 g_set_error (error, IANJUTA_PROJECT_ERROR,
1079 IANJUTA_PROJECT_ERROR_NOT_SUPPORTED,
1080 _("Project doesn't allow to set properties"));
1082 return FALSE;
1085 static AnjutaProjectNode *
1086 iproject_get_root (IAnjutaProject *obj, GError **error)
1088 return ANJUTA_PROJECT_NODE (obj);
1091 static const GList*
1092 iproject_get_node_info (IAnjutaProject *obj, GError **err)
1094 return dir_project_get_node_info (DIR_PROJECT (obj), err);
1097 static gboolean
1098 iproject_is_loaded (IAnjutaProject *obj, GError **err)
1100 return dir_project_is_loaded (DIR_PROJECT (obj));
1103 static void
1104 iproject_iface_init(IAnjutaProjectIface* iface)
1106 iface->load_node = iproject_load_node;
1107 iface->save_node = iproject_save_node;
1108 iface->add_node_before = iproject_add_node_before;
1109 iface->add_node_after = iproject_add_node_after;
1110 iface->remove_node = iproject_remove_node;
1111 iface->set_property = iproject_set_property;
1112 iface->remove_property = iproject_remove_property;
1113 iface->get_root = iproject_get_root;
1114 iface->get_node_info = iproject_get_node_info;
1115 iface->is_loaded = iproject_is_loaded;
1118 /* GbfProject implementation
1119 *---------------------------------------------------------------------------*/
1121 static void
1122 dir_project_dispose (GObject *object)
1124 g_return_if_fail (DIR_IS_PROJECT (object));
1126 dir_project_unload (DIR_PROJECT (object));
1128 G_OBJECT_CLASS (parent_class)->dispose (object);
1131 static void
1132 dir_project_instance_init (DirProject *project)
1134 g_return_if_fail (project != NULL);
1135 g_return_if_fail (DIR_IS_PROJECT (project));
1137 project->monitors = NULL;
1138 project->groups = NULL;
1140 project->sources = NULL;
1143 static void
1144 dir_project_class_init (DirProjectClass *klass)
1146 GObjectClass *object_class;
1148 parent_class = g_type_class_peek_parent (klass);
1150 object_class = G_OBJECT_CLASS (klass);
1151 object_class->dispose = dir_project_dispose;
1154 ANJUTA_TYPE_BEGIN(DirProject, dir_project, ANJUTA_TYPE_DIR_ROOT_NODE);
1155 ANJUTA_TYPE_ADD_INTERFACE(iproject, IANJUTA_TYPE_PROJECT);
1156 ANJUTA_TYPE_END;