1 /*
2  * Copyright 2017 LarsGit223
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 /*
20  * Code for the WB_PROJECT structure.
21  */
22 #include <glib/gstdio.h>
23 #include <git2.h>
24 
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28 
29 #include <geanyplugin.h>
30 #include <../../utils/src/filelist.h>
31 #include "wb_globals.h"
32 #include "wb_project.h"
33 #include "sidebar.h"
34 #include "utils.h"
35 #include "idle_queue.h"
36 
37 extern GeanyData *geany_data;
38 
39 typedef enum
40 {
41 	WB_PROJECT_TAG_PREFS_AUTO,
42 	WB_PROJECT_TAG_PREFS_YES,
43 	WB_PROJECT_TAG_PREFS_NO,
44 }WB_PROJECT_TAG_PREFS;
45 
46 typedef struct
47 {
48 	GKeyFile *kf;
49 	guint    dir_count;
50 }WB_PROJECT_ON_SAVE_USER_DATA;
51 
52 struct S_WB_PROJECT_DIR
53 {
54 	gchar *name;
55 	gchar *base_dir;
56 	WB_PROJECT_SCAN_MODE scan_mode;
57 	gchar **file_patterns;	/**< Array of filename extension patterns. */
58 	gchar **ignored_dirs_patterns;
59 	gchar **ignored_file_patterns;
60 	git_repository *git_repo;
61 	guint file_count;
62 	guint subdir_count;
63 	GHashTable *file_table; /* contains all file names within base_dir */
64 	gboolean is_prj_base_dir;
65 };
66 
67 
68 typedef struct
69 {
70 	guint file_count;
71 	guint subdir_count;
72 	GSList *file_patterns_list;
73 	GSList *ignored_dirs_list;
74 	GSList *ignored_file_list;
75 	git_repository *git_repo;
76 }SCAN_PARAMS;
77 
78 
79 struct S_WB_PROJECT
80 {
81 	gchar     *filename;
82 	gchar     *name;
83 	gboolean  modified;
84 	GSList    *directories;  /* list of WB_PROJECT_DIR; */
85 	WB_PROJECT_TAG_PREFS generate_tag_prefs;
86 	GPtrArray *bookmarks;
87 };
88 
89 typedef struct
90 {
91 	guint len;
92 	const gchar *string;
93 }WB_PROJECT_TEMP_DATA;
94 
95 
96 /** Set the projects modified marker.
97  *
98  * @param prj   The project
99  * @param value The value to set
100  *
101  **/
wb_project_set_modified(WB_PROJECT * prj,gboolean value)102 void wb_project_set_modified(WB_PROJECT *prj, gboolean value)
103 {
104 	if (prj != NULL)
105 	{
106 		prj->modified = value;
107 	}
108 }
109 
110 
111 /** Has the project been modified since the last save?
112  *
113  * @param prj   The project
114  * @return TRUE if project is modified, FALSE otherwise
115  *
116  **/
wb_project_is_modified(WB_PROJECT * prj)117 gboolean wb_project_is_modified(WB_PROJECT *prj)
118 {
119 	if (prj != NULL)
120 	{
121 		return (prj->modified);
122 	}
123 	return FALSE;
124 }
125 
126 
127 /** Set the filename of a project.
128  *
129  * @param prj      The project
130  * @param filename The filename
131  *
132  **/
wb_project_set_filename(WB_PROJECT * prj,const gchar * filename)133 void wb_project_set_filename(WB_PROJECT *prj, const gchar *filename)
134 {
135 	if (prj != NULL)
136 	{
137 		guint offset;
138 		gchar *ext;
139 
140 		g_free(prj->filename);
141 		prj->filename = g_strdup(filename);
142 		g_free(prj->name);
143 		prj->name = g_path_get_basename (filename);
144 		ext = g_strrstr(prj->name, ".geany");
145 		if(ext != NULL)
146 		{
147 			offset = strlen(prj->name);
148 			offset -= strlen(".geany");
149 			if (ext == prj->name + offset)
150 			{
151 				/* Strip of file extension by overwriting
152 				   '.' with string terminator. */
153 				prj->name[offset] = '\0';
154 			}
155 		}
156 	}
157 }
158 
159 
160 /** Get the filename of a project.
161  *
162  * @param prj The project
163  * @return The filename
164  *
165  **/
wb_project_get_filename(WB_PROJECT * prj)166 const gchar *wb_project_get_filename(WB_PROJECT *prj)
167 {
168 	if (prj != NULL)
169 	{
170 		return prj->filename;
171 	}
172 	return NULL;
173 }
174 
175 
176 /** Get the name of a project.
177  *
178  * @param prj The project
179  * @return The project name
180  *
181  **/
wb_project_get_name(WB_PROJECT * prj)182 const gchar *wb_project_get_name(WB_PROJECT *prj)
183 {
184 	if (prj != NULL)
185 	{
186 		return prj->name;
187 	}
188 	return NULL;
189 }
190 
191 
192 /** Get the list of directories contained in the project.
193  *
194  * @param prj The project
195  * @return GSList of directories (WB_PROJECT_DIRs)
196  *
197  **/
wb_project_get_directories(WB_PROJECT * prj)198 GSList *wb_project_get_directories(WB_PROJECT *prj)
199 {
200 	if (prj != NULL)
201 	{
202 		return prj->directories;
203 	}
204 	return NULL;
205 }
206 
207 
208 /* Create a new project dir with base path "utf8_base_dir" */
wb_project_dir_new(WB_PROJECT * prj,const gchar * utf8_base_dir)209 static WB_PROJECT_DIR *wb_project_dir_new(WB_PROJECT *prj, const gchar *utf8_base_dir)
210 {
211 	guint offset;
212 
213 	if (utf8_base_dir == NULL)
214 	{
215 		return NULL;
216 	}
217 	WB_PROJECT_DIR *dir = g_new0(WB_PROJECT_DIR, 1);
218 	dir->base_dir = g_strdup(utf8_base_dir);
219 	dir->file_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
220 	dir->scan_mode = WB_PROJECT_SCAN_MODE_WORKBENCH;
221 
222 	offset = strlen(dir->base_dir)-1;
223 	while (offset > 0
224 		   && dir->base_dir[offset] != '\\'
225 		   && dir->base_dir[offset] != '/')
226 	{
227 		offset--;
228 	}
229 	if (offset != 0)
230 	{
231 		offset++;
232 	}
233 	dir->name = g_strdup(&(dir->base_dir[offset]));
234 	dir->is_prj_base_dir = FALSE;
235 
236 	return dir;
237 }
238 
239 
240 /* Collect source files */
wb_project_dir_collect_source_files(G_GNUC_UNUSED gchar * filename,gpointer * value,gpointer user_data)241 static void wb_project_dir_collect_source_files(G_GNUC_UNUSED gchar *filename, gpointer *value, gpointer user_data)
242 {
243 	GPtrArray *array = user_data;
244 	g_ptr_array_add(array, g_strdup(filename));
245 }
246 
247 
248 /** Set "is project base dir" of a project dir.
249  *
250  * @param directory The project dir
251  * @param value     TRUE:  directory is the base dir of the project
252  *                  FALSE: directory is another dir
253  *
254  **/
wb_project_dir_set_is_prj_base_dir(WB_PROJECT_DIR * directory,gboolean value)255 void wb_project_dir_set_is_prj_base_dir (WB_PROJECT_DIR *directory, gboolean value)
256 {
257 	if (directory != NULL)
258 	{
259 		directory->is_prj_base_dir = value;
260 	}
261 }
262 
263 
264 /** Get "is project base dir" of a project dir.
265  *
266  * @param directory The project dir
267  * @return TRUE:  directory is the base dir of the project
268  *         FALSE: directory is another dir
269  *
270  **/
wb_project_dir_get_is_prj_base_dir(WB_PROJECT_DIR * directory)271 gboolean wb_project_dir_get_is_prj_base_dir (WB_PROJECT_DIR *directory)
272 {
273 	if (directory != NULL)
274 	{
275 		return directory->is_prj_base_dir;
276 	}
277 	return FALSE;
278 }
279 
280 
281 /** Get the name of a project dir.
282  *
283  * @param directory The project dir
284  * @return The name
285  *
286  **/
wb_project_dir_get_name(WB_PROJECT_DIR * directory)287 const gchar *wb_project_dir_get_name (WB_PROJECT_DIR *directory)
288 {
289 	if (directory != NULL)
290 	{
291 		return directory->name;
292 	}
293 	return NULL;
294 }
295 
296 
297 /** Get the file table for the project dir.
298  *
299  * @param directory The project dir
300  * @return A GHashTable of all files containing to the rpoject dir.
301  *         Might be empty if dir has not been scanned yet.
302  *
303  **/
wb_project_dir_get_file_table(WB_PROJECT_DIR * directory)304 GHashTable *wb_project_dir_get_file_table (WB_PROJECT_DIR *directory)
305 {
306 	if (directory != NULL)
307 	{
308 		return directory->file_table;
309 	}
310 	return NULL;
311 }
312 
313 
314 /** Get the base/root dir of a project dir.
315  *
316  * @param directory The project dir
317  * @return The base dir
318  *
319  **/
wb_project_dir_get_base_dir(WB_PROJECT_DIR * directory)320 gchar *wb_project_dir_get_base_dir (WB_PROJECT_DIR *directory)
321 {
322 	if (directory != NULL)
323 	{
324 		return directory->base_dir;
325 	}
326 	return NULL;
327 }
328 
329 
330 /** Get the file patterns of a project dir.
331  *
332  * @param directory The project dir
333  * @return String array of file patterns
334  *
335  **/
wb_project_dir_get_file_patterns(WB_PROJECT_DIR * directory)336 gchar **wb_project_dir_get_file_patterns (WB_PROJECT_DIR *directory)
337 {
338 	if (directory != NULL)
339 	{
340 		return directory->file_patterns;
341 	}
342 	return NULL;
343 }
344 
345 
346 /** Set the file patterns of a project dir.
347  *
348  * @param directory The project dir
349  * @param new       String array of file patterns to set
350  * @return FALSE if directory is NULL, TRUE otherwise
351  *
352  **/
wb_project_dir_set_file_patterns(WB_PROJECT_DIR * directory,gchar ** new)353 gboolean wb_project_dir_set_file_patterns (WB_PROJECT_DIR *directory, gchar **new)
354 {
355 	if (directory != NULL)
356 	{
357 		g_strfreev(directory->file_patterns);
358 		directory->file_patterns = g_strdupv(new);
359 		return TRUE;
360 	}
361 	return FALSE;
362 }
363 
364 
365 /** Get the ignored dirs patterns of a project dir.
366  *
367  * @param directory The project dir
368  * @return String array of ignored dirs patterns
369  *
370  **/
wb_project_dir_get_ignored_dirs_patterns(WB_PROJECT_DIR * directory)371 gchar **wb_project_dir_get_ignored_dirs_patterns (WB_PROJECT_DIR *directory)
372 {
373 	if (directory != NULL)
374 	{
375 		return directory->ignored_dirs_patterns;
376 	}
377 	return NULL;
378 }
379 
380 
381 /** Set the ignored dirs patterns of a project dir.
382  *
383  * @param directory The project dir
384  * @param new       String array of ignored dirs patterns to set
385  * @return FALSE if directory is NULL, TRUE otherwise
386  *
387  **/
wb_project_dir_set_ignored_dirs_patterns(WB_PROJECT_DIR * directory,gchar ** new)388 gboolean wb_project_dir_set_ignored_dirs_patterns (WB_PROJECT_DIR *directory, gchar **new)
389 {
390 	if (directory != NULL)
391 	{
392 		g_strfreev(directory->ignored_dirs_patterns);
393 		directory->ignored_dirs_patterns = g_strdupv(new);
394 		return TRUE;
395 	}
396 	return FALSE;
397 }
398 
399 
400 /** Get the ignored file patterns of a project dir.
401  *
402  * @param directory The project dir
403  * @return String array of ignored file patterns
404  *
405  **/
wb_project_dir_get_ignored_file_patterns(WB_PROJECT_DIR * directory)406 gchar **wb_project_dir_get_ignored_file_patterns (WB_PROJECT_DIR *directory)
407 {
408 	if (directory != NULL)
409 	{
410 		return directory->ignored_file_patterns;
411 	}
412 	return NULL;
413 }
414 
415 
416 /** Set the ignored file patterns of a project dir.
417  *
418  * @param directory The project dir
419  * @param new       String array of ignored dirs patterns to set
420  * @return FALSE if directory is NULL, TRUE otherwise
421  *
422  **/
wb_project_dir_set_ignored_file_patterns(WB_PROJECT_DIR * directory,gchar ** new)423 gboolean wb_project_dir_set_ignored_file_patterns (WB_PROJECT_DIR *directory, gchar **new)
424 {
425 	if (directory != NULL)
426 	{
427 		g_strfreev(directory->ignored_file_patterns);
428 		directory->ignored_file_patterns = g_strdupv(new);
429 		return TRUE;
430 	}
431 	return FALSE;
432 }
433 
434 
435 /** Get the scan mode of a project dir.
436  *
437  * @param directory The project dir
438  * @return WB_PROJECT_SCAN_MODE (the scan mode)
439  *
440  **/
wb_project_dir_get_scan_mode(WB_PROJECT_DIR * directory)441 WB_PROJECT_SCAN_MODE wb_project_dir_get_scan_mode (WB_PROJECT_DIR *directory)
442 {
443 	if (directory != NULL)
444 	{
445 		return directory->scan_mode;
446 	}
447 
448 	return WB_PROJECT_SCAN_MODE_INVALID;
449 }
450 
451 
452 /* Open or close the git repository in directory as required. */
wb_project_dir_prepare_git_repo(WB_PROJECT * project,WB_PROJECT_DIR * directory)453 static void wb_project_dir_prepare_git_repo(WB_PROJECT *project, WB_PROJECT_DIR *directory)
454 {
455 	gchar *path;
456 
457 	path = get_combined_path(project->filename, directory->base_dir);
458 	if (directory->scan_mode == WB_PROJECT_SCAN_MODE_GIT)
459 	{
460 		if (directory->git_repo == NULL)
461 		{
462 			if (git_repository_open(&(directory->git_repo), path) != 0)
463 			{
464 				directory->git_repo = NULL;
465 				ui_set_statusbar(TRUE,
466 					_("Failed to open git repository in folder %s."), path);
467 			}
468 			else
469 			{
470 				ui_set_statusbar(TRUE,
471 					_("Opened git repository in folder %s."), path);
472 			}
473 		}
474 	}
475 	else
476 	{
477 		if (directory->git_repo != NULL)
478 		{
479 			git_repository_free(directory->git_repo);
480 			directory->git_repo = NULL;
481 			ui_set_statusbar(TRUE,
482 				_("Closed git repository in folder %s."), path);
483 		}
484 	}
485 	g_free(path);
486 }
487 
488 
489 /** Set the scan mode of a project dir.
490  *
491  * @param directory The project dir
492  * @param mode      The scan mode to set
493  * @return FALSE if directory is NULL, TRUE otherwise
494  *
495  **/
wb_project_dir_set_scan_mode(WB_PROJECT * project,WB_PROJECT_DIR * directory,WB_PROJECT_SCAN_MODE mode)496 gboolean wb_project_dir_set_scan_mode(WB_PROJECT *project, WB_PROJECT_DIR *directory, WB_PROJECT_SCAN_MODE mode)
497 {
498 	if (directory != NULL)
499 	{
500 		directory->scan_mode = mode;
501 		wb_project_dir_prepare_git_repo(project, directory);
502 		return TRUE;
503 	}
504 	return FALSE;
505 }
506 
507 
508 /* Remove all files contained in the project dir from the tm-workspace */
wb_project_dir_remove_from_tm_workspace(WB_PROJECT_DIR * root)509 static void wb_project_dir_remove_from_tm_workspace(WB_PROJECT_DIR *root)
510 {
511 	GPtrArray *files;
512 
513 	files = g_ptr_array_new();
514 	g_hash_table_foreach(root->file_table, (GHFunc)wb_project_dir_collect_source_files, files);
515 	wb_idle_queue_add_action(WB_IDLE_ACTION_ID_TM_SOURCE_FILES_REMOVE, files);
516 }
517 
518 
519 /* Free a project dir */
wb_project_dir_free(WB_PROJECT_DIR * dir)520 static void wb_project_dir_free(WB_PROJECT_DIR *dir)
521 {
522 	wb_project_dir_remove_from_tm_workspace(dir);
523 
524 	g_hash_table_destroy(dir->file_table);
525 	g_free(dir->base_dir);
526 	g_free(dir);
527 }
528 
529 
530 /* Compare two project dirs */
wb_project_dir_comparator(WB_PROJECT_DIR * a,WB_PROJECT_DIR * b)531 static gint wb_project_dir_comparator(WB_PROJECT_DIR *a, WB_PROJECT_DIR *b)
532 {
533 	gchar *a_realpath, *b_realpath, *a_locale_base_dir, *b_locale_base_dir;
534 	gint res;
535 
536 	a_locale_base_dir = utils_get_locale_from_utf8(a->base_dir);
537 	b_locale_base_dir = utils_get_locale_from_utf8(b->base_dir);
538 	a_realpath = utils_get_real_path(a_locale_base_dir);
539 	b_realpath = utils_get_real_path(b_locale_base_dir);
540 
541 	res = g_strcmp0(a_realpath, b_realpath);
542 
543 	g_free(a_realpath);
544 	g_free(b_realpath);
545 	g_free(a_locale_base_dir);
546 	g_free(b_locale_base_dir);
547 
548 	return res;
549 }
550 
551 
552 /* Get the file count of a project */
wb_project_get_file_count(WB_PROJECT * prj)553 static guint wb_project_get_file_count(WB_PROJECT *prj)
554 {
555 	GSList *elem = NULL;
556 	guint filenum = 0;
557 
558 	foreach_slist(elem, prj->directories)
559 	{
560 		filenum += ((WB_PROJECT_DIR *)elem->data)->file_count;
561 	}
562 	return filenum;
563 }
564 
565 
566 /* Callback function for 'gp_filelist_scan_directory_callback', scan mode
567    'workbench', the plugin decides which files to add to the filelist. */
scan_mode_workbench_cb(const gchar * path,gboolean * add,gboolean * enter,void * userdata)568 static void scan_mode_workbench_cb(const gchar *path, gboolean *add, gboolean *enter, void *userdata)
569 {
570 	SCAN_PARAMS *params = userdata;
571 
572 	*enter = FALSE;
573 	*add = FALSE;
574 
575 	if (g_file_test(path, G_FILE_TEST_IS_DIR))
576 	{
577 		if (!filelist_patterns_match(params->ignored_dirs_list, path))
578 		{
579 			*enter = TRUE;
580 			*add = TRUE;
581 		}
582 	}
583 	else if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
584 	{
585 		if (filelist_patterns_match(params->file_patterns_list, path) &&
586 			!filelist_patterns_match(params->ignored_file_list, path))
587 		{
588 			*enter = TRUE;
589 			*add = TRUE;
590 		}
591 	}
592 }
593 
594 
595 /* Callback function for 'gp_filelist_scan_directory_callback', scan mode
596    'git', libgit2 decides which files to add to the filelist. */
scan_mode_git_cb(const gchar * path,gboolean * add,gboolean * enter,void * userdata)597 static void scan_mode_git_cb(const gchar *path, gboolean *add, gboolean *enter, void *userdata)
598 {
599 	gint ignored;
600 	SCAN_PARAMS *params = userdata;
601 
602 	*enter = TRUE;
603 	*add = TRUE;
604 	if (params->git_repo != NULL)
605 	{
606 		git_ignore_path_is_ignored(&ignored, params->git_repo, path);
607 		if (ignored > 0)
608 		{
609 			*enter = FALSE;
610 			*add = FALSE;
611 		}
612 	}
613 }
614 
615 
616 /* Scan a path according to the settings given in parameter root. */
wb_project_dir_scan_directory(WB_PROJECT_DIR * root,const gchar * searchdir,guint * file_count,guint * subdir_count)617 static GSList *wb_project_dir_scan_directory(WB_PROJECT_DIR *root, const gchar *searchdir,
618 											 guint *file_count, guint *subdir_count)
619 {
620 	GSList *filelist;
621 	SCAN_PARAMS params = { 0 };
622 
623 	if (root->scan_mode != WB_PROJECT_SCAN_MODE_GIT)
624 	{
625 		if (!root->file_patterns || !root->file_patterns[0])
626 		{
627 			const gchar *all_pattern[] = { "*", NULL };
628 			params.file_patterns_list = filelist_get_precompiled_patterns((gchar **)all_pattern);
629 		}
630 		else
631 		{
632 			params.file_patterns_list = filelist_get_precompiled_patterns(root->file_patterns);
633 		}
634 
635 		params.ignored_dirs_list = filelist_get_precompiled_patterns(root->ignored_dirs_patterns);
636 		params.ignored_file_list = filelist_get_precompiled_patterns(root->ignored_file_patterns);
637 
638 		filelist = gp_filelist_scan_directory_callback
639 						(searchdir, scan_mode_workbench_cb, &params);
640 
641 		g_slist_foreach(params.file_patterns_list, (GFunc) g_pattern_spec_free, NULL);
642 		g_slist_free(params.file_patterns_list);
643 
644 		g_slist_foreach(params.ignored_dirs_list, (GFunc) g_pattern_spec_free, NULL);
645 		g_slist_free(params.ignored_dirs_list);
646 
647 		g_slist_foreach(params.ignored_file_list, (GFunc) g_pattern_spec_free, NULL);
648 		g_slist_free(params.ignored_file_list);
649 	}
650 	else
651 	{
652 		params.git_repo = root->git_repo;
653 		filelist = gp_filelist_scan_directory_callback
654 						(searchdir, scan_mode_git_cb, &params);
655 	}
656 
657 	if (file_count != NULL)
658 	{
659 		*file_count = params.file_count;
660 	}
661 	if (subdir_count != NULL)
662 	{
663 		*subdir_count = params.subdir_count;
664 	}
665 
666 	return filelist;
667 }
668 
669 
670 /* Rescan/update the file list of a project dir. */
wb_project_dir_rescan_int(WB_PROJECT * prj,WB_PROJECT_DIR * root)671 static guint wb_project_dir_rescan_int(WB_PROJECT *prj, WB_PROJECT_DIR *root)
672 {
673 	GSList *lst;
674 	GSList *elem = NULL;
675 	guint filenum = 0;
676 	gchar *searchdir;
677 
678 	wb_project_dir_remove_from_tm_workspace(root);
679 	g_hash_table_remove_all(root->file_table);
680 
681 	searchdir = get_combined_path(prj->filename, root->base_dir);
682 	root->file_count = 0;
683 	root->subdir_count = 0;
684 	lst = wb_project_dir_scan_directory
685 			(root, searchdir, &(root->file_count), &(root->subdir_count));
686 	g_free(searchdir);
687 
688 	foreach_slist(elem, lst)
689 	{
690 		char *path = elem->data;
691 
692 		if (path)
693 		{
694 			g_hash_table_add(root->file_table, g_strdup(path));
695 			filenum++;
696 		}
697 	}
698 
699 	g_slist_foreach(lst, (GFunc) g_free, NULL);
700 	g_slist_free(lst);
701 
702 	return filenum;
703 }
704 
705 
706 /* Single check if a path is to be ignored or not. */
wb_project_dir_path_is_ignored(WB_PROJECT_DIR * root,const gchar * filepath)707 static gboolean wb_project_dir_path_is_ignored(WB_PROJECT_DIR *root, const gchar *filepath)
708 {
709 	if (root->scan_mode == WB_PROJECT_SCAN_MODE_WORKBENCH)
710 	{
711 		gchar **file_patterns = NULL;
712 		gboolean matches;
713 
714 		if (root->file_patterns && root->file_patterns[0])
715 		{
716 			file_patterns = root->file_patterns;
717 		}
718 
719 		matches = gp_filelist_filepath_matches_patterns(filepath,
720 			file_patterns, root->ignored_dirs_patterns, root->ignored_file_patterns);
721 		if (!matches)
722 		{
723 			/* Ignore it. */
724 			return TRUE;
725 		}
726 	}
727 	else
728 	{
729 		if (root->git_repo != NULL)
730 		{
731 			gint ignored;
732 
733 			git_ignore_path_is_ignored(&ignored, root->git_repo, filepath);
734 			if (ignored > 0)
735 			{
736 				/* Ignore it. */
737 				return TRUE;
738 			}
739 		}
740 	}
741 
742 	/* Do not ignore it. */
743 	return FALSE;
744 }
745 
746 
747 /* Add a new file to the project directory and update the sidebar. */
wb_project_dir_add_file_int(WB_PROJECT * prj,WB_PROJECT_DIR * root,const gchar * filepath)748 static void wb_project_dir_add_file_int(WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
749 {
750 	SIDEBAR_CONTEXT context;
751 	WB_MONITOR *monitor = NULL;
752 
753 	if (wb_project_dir_path_is_ignored(root, filepath))
754 	{
755 		/* Ignore it. */
756 		return;
757 	}
758 
759 	/* Update file table and counters. */
760 	g_hash_table_add(root->file_table, g_strdup(filepath));
761 	if (g_file_test(filepath, G_FILE_TEST_IS_DIR))
762 	{
763 		root->subdir_count++;
764 		monitor = workbench_get_monitor(wb_globals.opened_wb);
765 		wb_monitor_add_dir(monitor, prj, root, filepath);
766 	}
767 	else if (g_file_test(filepath, G_FILE_TEST_IS_REGULAR))
768 	{
769 		root->file_count++;
770 	}
771 
772 	/* Update sidebar. */
773 	memset(&context, 0, sizeof(context));
774 	context.project = prj;
775 	context.directory = root;
776 	context.file = (gchar *)filepath;
777 	sidebar_update(SIDEBAR_CONTEXT_FILE_ADDED, &context);
778 
779 	/* If the file is a directory we also have to manually add all files
780 	   contained in it. */
781 	if (monitor != NULL)
782 	{
783 		GSList *scanned, *elem = NULL;
784 
785 		scanned = wb_project_dir_scan_directory
786 				(root, filepath, &(root->file_count), &(root->subdir_count));
787 
788 		foreach_slist(elem, scanned)
789 		{
790 			char *path = elem->data;
791 
792 			if (path)
793 			{
794 				wb_project_dir_add_file(prj, root, path);
795 			}
796 		}
797 
798 		g_slist_foreach(scanned, (GFunc) g_free, NULL);
799 		g_slist_free(scanned);
800 	}
801 }
802 
803 
804 /* Update tags for new files */
wb_project_dir_update_tags(WB_PROJECT_DIR * root)805 static void wb_project_dir_update_tags(WB_PROJECT_DIR *root)
806 {
807 	GHashTableIter iter;
808 	gpointer key, value;
809 	GPtrArray *files;
810 
811 	files = g_ptr_array_new_full(1, g_free);
812 	g_hash_table_iter_init(&iter, root->file_table);
813 	while (g_hash_table_iter_next(&iter, &key, &value))
814 	{
815 		if (value == NULL)
816 		{
817 			gchar *utf8_path = key;
818 			gchar *locale_path = utils_get_locale_from_utf8(utf8_path);
819 
820 			g_ptr_array_add(files, g_strdup(key));
821 			g_hash_table_add(root->file_table, g_strdup(utf8_path));
822 			g_free(locale_path);
823 		}
824 	}
825 
826 	wb_idle_queue_add_action(WB_IDLE_ACTION_ID_TM_SOURCE_FILES_NEW, files);
827 }
828 
829 
830 /** Add a new file to the project directory and update the sidebar.
831  *
832  * The file is only added if it matches the pattern settings.
833  *
834  * @param prj      The project to add it to.
835  * @param root     The directory to add it to.
836  * @param filepath The file to add.
837  *
838  **/
wb_project_dir_add_file(WB_PROJECT * prj,WB_PROJECT_DIR * root,const gchar * filepath)839 void wb_project_dir_add_file(WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
840 {
841 	wb_project_dir_add_file_int(prj, root, filepath);
842 	wb_project_dir_update_tags(root);
843 }
844 
845 
846 /* Check if the filepath is equal for the length of the directory path in px_temp */
wb_project_dir_remove_child(gpointer key,gpointer value,gpointer user_data)847 static gboolean wb_project_dir_remove_child (gpointer key, gpointer value, gpointer user_data)
848 {
849 	WB_PROJECT_TEMP_DATA *px_temp;
850 
851 	px_temp = user_data;
852 	if (strncmp(px_temp->string, key, px_temp->len) == 0)
853 	{
854 		/* We found a child of our removed directory.
855 		   Remove it from the hash table. This will also free
856 		   the tags. We do not need to update the sidebar as we
857 		   already deleted the parent directory/node. */
858 		wb_idle_queue_add_action(WB_IDLE_ACTION_ID_TM_SOURCE_FILE_REMOVE, g_strdup(key));
859 		return TRUE;
860 	}
861 	return FALSE;
862 }
863 
864 
865 /** Remove a file from the project directory and update the sidebar.
866  *
867  * If the file still exists, it is only removed if it matches the pattern settings.
868  *
869  * @param prj      The project to remove it from.
870  * @param root     The directory to remove it from.
871  * @param filepath The file to remove.
872  *
873  **/
wb_project_dir_remove_file(WB_PROJECT * prj,WB_PROJECT_DIR * root,const gchar * filepath)874 void wb_project_dir_remove_file(WB_PROJECT *prj, WB_PROJECT_DIR *root, const gchar *filepath)
875 {
876 	gboolean matches, was_dir;
877 	WB_MONITOR *monitor;
878 
879 	if (g_file_test(filepath, G_FILE_TEST_EXISTS))
880 	{
881 		matches = FALSE;
882 		if (wb_project_dir_path_is_ignored(root, filepath) == FALSE)
883 		{
884 			matches = TRUE;
885 		}
886 	}
887 	else
888 	{
889 		/* If the file does not exist any more, then always try to remove it. */
890 		matches = TRUE;
891 	}
892 
893 	if (matches)
894 	{
895 		SIDEBAR_CONTEXT context;
896 
897 		/* Update file table and counters. */
898 		wb_idle_queue_add_action(WB_IDLE_ACTION_ID_TM_SOURCE_FILE_REMOVE,
899 			g_strdup(filepath));
900 		g_hash_table_remove(root->file_table, filepath);
901 
902 		/* If the file already has been deleted, we cannot determine if it
903 		   was a file or directory at this point. But the monitors will
904 		   help us out, see code at end of function. */
905 
906 		/* Update sidebar. */
907 		memset(&context, 0, sizeof(context));
908 		context.project = prj;
909 		context.directory = root;
910 		context.file = (gchar *)filepath;
911 		sidebar_update(SIDEBAR_CONTEXT_FILE_REMOVED, &context);
912 	}
913 
914 	/* Remove the file monitor for filepath. This will only return TRUE
915 	   if there is a file monitor for filepath and that means that file-
916 	   path was a directory. So we can determine if filepath was a dir
917 	   or not even if it has been deleted. */
918 	monitor = workbench_get_monitor(wb_globals.opened_wb);
919 	was_dir = wb_monitor_remove_dir(monitor, filepath);
920 	if (was_dir)
921 	{
922 		WB_PROJECT_TEMP_DATA x_temp;
923 
924 		x_temp.len = strlen(filepath);
925 		x_temp.string = filepath;
926 		g_hash_table_foreach_remove(root->file_table,
927 			wb_project_dir_remove_child, &x_temp);
928 
929 		if (root->subdir_count > 0)
930 		{
931 			root->subdir_count--;
932 		}
933 	}
934 	else
935 	{
936 		if (root->file_count > 0)
937 		{
938 			root->file_count--;
939 		}
940 	}
941 }
942 
943 
944 /* Regenerate tags */
wb_project_dir_regenerate_tags(WB_PROJECT_DIR * root,G_GNUC_UNUSED gpointer user_data)945 static void wb_project_dir_regenerate_tags(WB_PROJECT_DIR *root, G_GNUC_UNUSED gpointer user_data)
946 {
947 	GHashTableIter iter;
948 	gpointer key, value;
949 	GPtrArray *files;
950 	GHashTable *file_table;
951 
952 	files = g_ptr_array_new_full (1, g_free);
953 	file_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
954 	g_hash_table_iter_init(&iter, root->file_table);
955 	while (g_hash_table_iter_next(&iter, &key, &value))
956 	{
957 		if (g_file_test(key, G_FILE_TEST_IS_REGULAR))
958 		{
959 			g_ptr_array_add(files, g_strdup(key));
960 		}
961 
962 		/* Add all files to the file-table (files and dirs)! */
963 		g_hash_table_add(file_table, g_strdup(key));
964 	}
965 	g_hash_table_destroy(root->file_table);
966 	root->file_table = file_table;
967 
968 	wb_idle_queue_add_action(WB_IDLE_ACTION_ID_TM_SOURCE_FILES_NEW, files);
969 }
970 
971 
972 /** Rescan/update the file list of a project dir.
973  *
974  * @param project   The project to which directory belongs
975  * @param directory The project dir
976  * @return Number of files
977  *
978  **/
wb_project_dir_rescan(WB_PROJECT * prj,WB_PROJECT_DIR * root)979 guint wb_project_dir_rescan(WB_PROJECT *prj, WB_PROJECT_DIR *root)
980 {
981 	guint total, filenum;
982 
983 	filenum = wb_project_dir_rescan_int(prj, root);
984 	total = wb_project_get_file_count(prj);
985 	if (prj->generate_tag_prefs == WB_PROJECT_TAG_PREFS_YES || (prj->generate_tag_prefs == WB_PROJECT_TAG_PREFS_AUTO && total < 300))
986 	{
987 		wb_project_dir_regenerate_tags(root, NULL);
988 	}
989 	return filenum;
990 }
991 
992 
993 /** Rescan/update the file list of a project.
994  *
995  * @param project   The project
996  *
997  **/
wb_project_rescan(WB_PROJECT * prj)998 void wb_project_rescan(WB_PROJECT *prj)
999 {
1000 	GSList *elem = NULL;
1001 	guint filenum = 0;
1002 	GHashTableIter iter;
1003 
1004 	if (!prj)
1005 	{
1006 		return;
1007 	}
1008 
1009 	foreach_slist(elem, prj->directories)
1010 	{
1011 		filenum += wb_project_dir_rescan_int(prj, elem->data);
1012 	}
1013 
1014 	if (prj->generate_tag_prefs == WB_PROJECT_TAG_PREFS_YES || (prj->generate_tag_prefs == WB_PROJECT_TAG_PREFS_AUTO && filenum < 300))
1015 	{
1016 		g_slist_foreach(prj->directories, (GFunc)wb_project_dir_regenerate_tags, NULL);
1017 	}
1018 
1019 	/* Create file monitors for directories. */
1020 	if (workbench_get_enable_live_update(wb_globals.opened_wb) == TRUE)
1021 	{
1022 		WB_MONITOR *monitor;
1023 
1024 		monitor = workbench_get_monitor(wb_globals.opened_wb);
1025 		foreach_slist(elem, prj->directories)
1026 		{
1027 			gpointer path, value;
1028 			GHashTable *filehash;
1029 			gchar *abs_path;
1030 
1031 			/* First add monitor for base dir */
1032 			abs_path = get_combined_path(wb_project_get_filename(prj),
1033 										wb_project_dir_get_base_dir(elem->data));
1034 			wb_monitor_add_dir(monitor, prj, elem->data, abs_path);
1035 			g_free(abs_path);
1036 
1037 			/* Now add all dirs in file table */
1038 			filehash = ((WB_PROJECT_DIR *)elem->data)->file_table;
1039 			g_hash_table_iter_init(&iter, filehash);
1040 			while (g_hash_table_iter_next (&iter, &path, &value))
1041 			{
1042 				if (path != NULL && g_file_test(path, G_FILE_TEST_IS_DIR))
1043 				{
1044 					wb_monitor_add_dir(monitor, prj, elem->data, path);
1045 				}
1046 			}
1047 		}
1048 	}
1049 }
1050 
1051 
1052 /** Is @a filename included in the project directory?
1053  *
1054  * @param dir      The project directory
1055  * @param filename The file
1056  * @return TRUE if file is included, FALSE otherwise
1057  *
1058  **/
wb_project_dir_file_is_included(WB_PROJECT_DIR * dir,const gchar * filename)1059 gboolean wb_project_dir_file_is_included(WB_PROJECT_DIR *dir, const gchar *filename)
1060 {
1061 	if (filename == NULL || dir == NULL)
1062 	{
1063 		return FALSE;
1064 	}
1065 
1066 	if (g_hash_table_lookup_extended (dir->file_table, filename, NULL, NULL))
1067 	{
1068 		return TRUE;
1069 	}
1070 
1071 	return FALSE;
1072 }
1073 
1074 
1075 /** Is @a filename included in the project?
1076  *
1077  * @param dir      The project
1078  * @param filename The file
1079  * @return TRUE if file is included, FALSE otherwise
1080  *
1081  **/
wb_project_file_is_included(WB_PROJECT * prj,const gchar * filename)1082 gboolean wb_project_file_is_included(WB_PROJECT *prj, const gchar *filename)
1083 {
1084 	GSList *elem = NULL;
1085 
1086 	if (prj == NULL)
1087 	{
1088 		return FALSE;
1089 	}
1090 
1091 	foreach_slist(elem, prj->directories)
1092 	{
1093 		if (wb_project_dir_file_is_included(elem->data, filename) == TRUE)
1094 		{
1095 			return TRUE;
1096 		}
1097 	}
1098 	return FALSE;
1099 }
1100 
1101 
1102 /* Add a directory to the project */
wb_project_add_directory_int(WB_PROJECT * prj,const gchar * dirname,gboolean rescan)1103 static WB_PROJECT_DIR *wb_project_add_directory_int(WB_PROJECT *prj, const gchar *dirname, gboolean rescan)
1104 {
1105 	if (prj != NULL)
1106 	{
1107 		WB_PROJECT_DIR *new_dir = wb_project_dir_new(prj, dirname);
1108 
1109 		if (prj->directories != NULL)
1110 		{
1111 			GSList *lst = prj->directories->next;
1112 			lst = g_slist_prepend(lst, new_dir);
1113 			lst = g_slist_sort(lst, (GCompareFunc)wb_project_dir_comparator);
1114 			prj->directories->next = lst;
1115 		}
1116 		else
1117 		{
1118 			prj->directories = g_slist_append(prj->directories, new_dir);
1119 		}
1120 
1121 		if (rescan)
1122 		{
1123 			wb_project_rescan(prj);
1124 		}
1125 		return new_dir;
1126 	}
1127 	return NULL;
1128 }
1129 
1130 
1131 /** Adds a new project dir to the project.
1132  *
1133  * @param project The project
1134  * @param dirname The base dir of the new project dir
1135  * @return TRUE on success, FALSE otherwise
1136  *
1137  **/
wb_project_add_directory(WB_PROJECT * prj,const gchar * dirname)1138 gboolean wb_project_add_directory(WB_PROJECT *prj, const gchar *dirname)
1139 {
1140 	gchar *reldirname;
1141 
1142 	/* Convert dirname to path relative to the project file */
1143 	reldirname = get_any_relative_path(prj->filename, dirname);
1144 
1145 	if (wb_project_add_directory_int(prj, reldirname, TRUE) != NULL)
1146 	{
1147 		prj->modified = TRUE;
1148 		return TRUE;
1149 	}
1150 
1151 	g_free(reldirname);
1152 	return FALSE;
1153 }
1154 
1155 
1156 /** Remove a project dir from the project.
1157  *
1158  * @param project The project
1159  * @param dir     The project dir to remove
1160  * @return FALSE on NULL parameter, TRUE otherwise
1161  *
1162  **/
wb_project_remove_directory(WB_PROJECT * prj,WB_PROJECT_DIR * dir)1163 gboolean wb_project_remove_directory (WB_PROJECT *prj, WB_PROJECT_DIR *dir)
1164 {
1165 	if (prj != NULL && dir != NULL)
1166 	{
1167 		prj->directories = g_slist_remove(prj->directories, dir);
1168 		wb_project_dir_free(dir);
1169 		wb_project_rescan(prj);
1170 		prj->modified = TRUE;
1171 	}
1172 	return FALSE;
1173 }
1174 
1175 
1176 /** Get an info string for the project dir.
1177  *
1178  * @param dir The project dir
1179  * @return The info string
1180  *
1181  **/
wb_project_dir_get_info(WB_PROJECT_DIR * dir)1182 gchar *wb_project_dir_get_info (WB_PROJECT_DIR *dir)
1183 {
1184 	gchar *str;
1185 
1186 	if (dir == NULL)
1187 		return g_strdup("");
1188 
1189 	GString *temp = g_string_new(NULL);
1190 	gchar *text;
1191 	g_string_append_printf(temp, _("Directory-Name: %s\n"), wb_project_dir_get_name(dir));
1192 	g_string_append_printf(temp, _("Base-Directory: %s\n"), wb_project_dir_get_base_dir(dir));
1193 
1194 	g_string_append(temp, _("File Patterns:"));
1195 	str = g_strjoinv(" ", dir->file_patterns);
1196 	if (str != NULL )
1197 	{
1198 		g_string_append_printf(temp, " %s\n", str);
1199 		g_free(str);
1200 	}
1201 	else
1202 	{
1203 		g_string_append(temp, "\n");
1204 	}
1205 
1206 	g_string_append(temp, _("Ignored Dir. Patterns:"));
1207 	str = g_strjoinv(" ", dir->ignored_dirs_patterns);
1208 	if (str != NULL )
1209 	{
1210 		g_string_append_printf(temp, " %s\n", str);
1211 		g_free(str);
1212 	}
1213 	else
1214 	{
1215 		g_string_append(temp, "\n");
1216 	}
1217 
1218 	g_string_append(temp, _("Ignored File Patterns:"));
1219 	str = g_strjoinv(" ", dir->ignored_file_patterns);
1220 	if (str != NULL )
1221 	{
1222 		g_string_append_printf(temp, " %s\n", str);
1223 		g_free(str);
1224 	}
1225 	else
1226 	{
1227 		g_string_append(temp, "\n");
1228 	}
1229 
1230 	g_string_append_printf(temp, _("Number of Sub-Directories: %u\n"), dir->subdir_count);
1231 	g_string_append_printf(temp, _("Number of Files: %u\n"), dir->file_count);
1232 
1233 	/* Steal string content */
1234 	text = temp->str;
1235 	g_string_free (temp, FALSE);
1236 
1237 	return text;
1238 }
1239 
1240 
1241 /** Get an info string for the project.
1242  *
1243  * @param prj The project
1244  * @return The info string
1245  *
1246  **/
wb_project_get_info(WB_PROJECT * prj)1247 gchar *wb_project_get_info (WB_PROJECT *prj)
1248 {
1249 	GString *temp = g_string_new(NULL);
1250 	gchar *text;
1251 
1252 	if (prj == NULL)
1253 		return g_strdup("");
1254 
1255 	g_string_append_printf(temp, _("Project: %s\n"), wb_project_get_name(prj));
1256 	g_string_append_printf(temp, _("File: %s\n"), wb_project_get_filename(prj));
1257 	g_string_append_printf(temp, _("Number of Directories: %u\n"), g_slist_length(prj->directories));
1258 	if (wb_project_is_modified(prj))
1259 	{
1260 		g_string_append(temp, _("\nThe project contains unsaved changes!\n"));
1261 	}
1262 
1263 	/* Steal string content */
1264 	text = temp->str;
1265 	g_string_free (temp, FALSE);
1266 
1267 	return text;
1268 }
1269 
1270 
1271 /* Save directories to key file */
wb_project_save_directories(gpointer data,gpointer user_data)1272 static void wb_project_save_directories (gpointer data, gpointer user_data)
1273 {
1274 	gchar key[250], *str;
1275 	WB_PROJECT_DIR *dir;
1276 	WB_PROJECT_ON_SAVE_USER_DATA *tmp;
1277 
1278 	if (data == NULL || user_data == NULL)
1279 	{
1280 		return;
1281 	}
1282 	tmp = (WB_PROJECT_ON_SAVE_USER_DATA *)user_data;
1283 	dir = (WB_PROJECT_DIR *)data;
1284 
1285 	if (wb_project_dir_get_is_prj_base_dir(dir) == TRUE)
1286 	{
1287 		g_key_file_set_string(tmp->kf, "Workbench", "Prj-BaseDir", dir->base_dir);
1288 
1289 		if (dir->scan_mode == WB_PROJECT_SCAN_MODE_WORKBENCH)
1290 		{
1291 			g_key_file_set_string(tmp->kf, "Workbench", "Prj-ScanMode", "Workbench");
1292 		}
1293 		else
1294 		{
1295 			g_key_file_set_string(tmp->kf, "Workbench", "Prj-ScanMode", "Git");
1296 		}
1297 
1298 		str = g_strjoinv(";", dir->file_patterns);
1299 		g_key_file_set_string(tmp->kf, "Workbench", "Prj-FilePatterns", str);
1300 		g_free(str);
1301 
1302 		str = g_strjoinv(";", dir->ignored_dirs_patterns);
1303 		g_key_file_set_string(tmp->kf, "Workbench", "Prj-IgnoredDirsPatterns", str);
1304 		g_free(str);
1305 
1306 		str = g_strjoinv(";", dir->ignored_file_patterns);
1307 		g_key_file_set_string(tmp->kf, "Workbench", "Prj-IgnoredFilePatterns", str);
1308 		g_free(str);
1309 	}
1310 	else
1311 	{
1312 		g_snprintf(key, sizeof(key), "Dir%u-BaseDir", tmp->dir_count);
1313 		g_key_file_set_string(tmp->kf, "Workbench", key, dir->base_dir);
1314 
1315 		g_snprintf(key, sizeof(key), "Dir%u-ScanMode", tmp->dir_count);
1316 		if (dir->scan_mode == WB_PROJECT_SCAN_MODE_WORKBENCH)
1317 		{
1318 			g_key_file_set_string(tmp->kf, "Workbench", key, "Workbench");
1319 		}
1320 		else
1321 		{
1322 			g_key_file_set_string(tmp->kf, "Workbench", key, "Git");
1323 		}
1324 
1325 		g_snprintf(key, sizeof(key), "Dir%u-FilePatterns", tmp->dir_count);
1326 		str = g_strjoinv(";", dir->file_patterns);
1327 		g_key_file_set_string(tmp->kf, "Workbench", key, str);
1328 		g_free(str);
1329 
1330 		g_snprintf(key, sizeof(key), "Dir%u-IgnoredDirsPatterns", tmp->dir_count);
1331 		str = g_strjoinv(";", dir->ignored_dirs_patterns);
1332 		g_key_file_set_string(tmp->kf, "Workbench", key, str);
1333 		g_free(str);
1334 
1335 		g_snprintf(key, sizeof(key), "Dir%u-IgnoredFilePatterns", tmp->dir_count);
1336 		str = g_strjoinv(";", dir->ignored_file_patterns);
1337 		g_key_file_set_string(tmp->kf, "Workbench", key, str);
1338 		g_free(str);
1339 
1340 		tmp->dir_count++;
1341 	}
1342 }
1343 
1344 
1345 /* Add a bookmark to the project */
wb_project_add_bookmark_int(WB_PROJECT * prj,const gchar * filename)1346 static gboolean wb_project_add_bookmark_int(WB_PROJECT *prj, const gchar *filename)
1347 {
1348 	if (prj != NULL)
1349 	{
1350 		gchar *new;
1351 
1352 		new = g_strdup(filename);
1353 		if (new != NULL)
1354 		{
1355 			g_ptr_array_add (prj->bookmarks, new);
1356 			return TRUE;
1357 		}
1358 	}
1359 	return FALSE;
1360 }
1361 
1362 
1363 /** Add a bookmark to the project.
1364  *
1365  * @param prj      The project
1366  * @param filename Bookmark
1367  * @return TRUE on success, FALSE otherwise
1368  *
1369  **/
wb_project_add_bookmark(WB_PROJECT * prj,const gchar * filename)1370 gboolean wb_project_add_bookmark(WB_PROJECT *prj, const gchar *filename)
1371 {
1372 	if (wb_project_add_bookmark_int(prj, filename) == TRUE)
1373 	{
1374 		prj->modified = TRUE;
1375 		return TRUE;
1376 	}
1377 	return FALSE;
1378 }
1379 
1380 
1381 /** Remove a bookmark from the project.
1382  *
1383  * @param prj      The project
1384  * @param filename Bookmark
1385  * @return TRUE on success, FALSE otherwise
1386  *
1387  **/
wb_project_remove_bookmark(WB_PROJECT * prj,const gchar * filename)1388 gboolean wb_project_remove_bookmark(WB_PROJECT *prj, const gchar *filename)
1389 {
1390 	if (prj != NULL)
1391 	{
1392 		guint index;
1393 		gchar *current;
1394 
1395 		for (index = 0 ; index < prj->bookmarks->len ; index++)
1396 		{
1397 			current = g_ptr_array_index(prj->bookmarks, index);
1398 			if (current == filename)
1399 			{
1400 				g_ptr_array_remove_index (prj->bookmarks, index);
1401 				prj->modified = TRUE;
1402 				return TRUE;
1403 			}
1404 		}
1405 	}
1406 	return FALSE;
1407 }
1408 
1409 
1410 /* Free all bookmarks */
wb_project_free_all_bookmarks(WB_PROJECT * prj)1411 static gboolean wb_project_free_all_bookmarks(WB_PROJECT *prj)
1412 {
1413 	if (prj != NULL)
1414 	{
1415 		guint index;
1416 		gchar *current;
1417 
1418 		for (index = 0 ; index < prj->bookmarks->len ; index++)
1419 		{
1420 			current = g_ptr_array_index(prj->bookmarks, index);
1421 			g_free(current);
1422 		}
1423 		g_ptr_array_free(prj->bookmarks, TRUE);
1424 	}
1425 	return FALSE;
1426 }
1427 
1428 
1429 /** Get the bookmark of a project at index @a index.
1430  *
1431  * @param prj   The project
1432  * @param index The index
1433  * @return The filename of the boomark (or NULL if prj is NULL)
1434  *
1435  **/
wb_project_get_bookmark_at_index(WB_PROJECT * prj,guint index)1436 gchar *wb_project_get_bookmark_at_index (WB_PROJECT *prj, guint index)
1437 {
1438 	if (prj != NULL)
1439 	{
1440 		gchar *file;
1441 		file = g_ptr_array_index(prj->bookmarks, index);
1442 		if (file == NULL)
1443 		{
1444 			return NULL;
1445 		}
1446 		return file;
1447 	}
1448 	return NULL;
1449 }
1450 
1451 
1452 /** Get the number of bookmarks of a project.
1453  *
1454  * @param prj   The project
1455  * @return The number of bookmarks
1456  *
1457  **/
wb_project_get_bookmarks_count(WB_PROJECT * prj)1458 guint wb_project_get_bookmarks_count(WB_PROJECT *prj)
1459 {
1460 	if (prj != NULL && prj->bookmarks != NULL)
1461 	{
1462 		return prj->bookmarks->len;
1463 	}
1464 	return 0;
1465 }
1466 
1467 
1468 /** Save a project.
1469  *
1470  * The function saves the project config file. Only the workbench plugin info
1471  * is changed, all other config options remain unchanged.
1472  *
1473  * @param prj   The project
1474  * @param error Location to store error info at
1475  * @return TRUE on success, FALSE otherwise
1476  *
1477  **/
wb_project_save(WB_PROJECT * prj,GError ** error)1478 gboolean wb_project_save(WB_PROJECT *prj, GError **error)
1479 {
1480 	GKeyFile *kf;
1481 	guint    index;
1482 	gchar    *contents;
1483 	gsize    length, boomarks_size;
1484 	gboolean success = FALSE;
1485 	WB_PROJECT_ON_SAVE_USER_DATA tmp;
1486 
1487 	g_return_val_if_fail(prj, FALSE);
1488 
1489 	/* Load existing data into GKeyFile */
1490 	kf = g_key_file_new ();
1491 	if (!g_key_file_load_from_file(kf, prj->filename, G_KEY_FILE_NONE, error))
1492 	{
1493 		return FALSE;
1494 	}
1495 
1496 	/* Remove existing, old data from our plugin */
1497 	g_key_file_remove_group (kf, "Workbench", NULL);
1498 
1499 	/* Save Project bookmarks as string list */
1500 	boomarks_size = wb_project_get_bookmarks_count(prj);
1501 	if (boomarks_size > 0)
1502 	{
1503 		gchar **bookmarks_strings, *file, *rel_path;
1504 
1505 		bookmarks_strings = g_new0(gchar *, boomarks_size+1);
1506 		for (index = 0 ; index < boomarks_size ; index++ )
1507 		{
1508 			file = wb_project_get_bookmark_at_index(prj, index);
1509 			rel_path = get_any_relative_path(prj->filename, file);
1510 
1511 			bookmarks_strings[index] = rel_path;
1512 		}
1513 		g_key_file_set_string_list
1514 			(kf, "Workbench", "Bookmarks", (const gchar **)bookmarks_strings, boomarks_size);
1515 		for (index = 0 ; index < boomarks_size ; index++ )
1516 		{
1517 			g_free (bookmarks_strings[index]);
1518 		}
1519 		g_free(bookmarks_strings);
1520 	}
1521 
1522 	/* Init tmp data */
1523 	tmp.kf = kf;
1524 	tmp.dir_count = 1;
1525 
1526 	/* Store our directories */
1527 	g_slist_foreach(prj->directories, (GFunc)wb_project_save_directories, &tmp);
1528 
1529 	/* Get data as string */
1530 	contents = g_key_file_to_data (kf, &length, error);
1531 	g_key_file_free(kf);
1532 
1533 	/* Save to file */
1534 	success = g_file_set_contents (prj->filename, contents, length, error);
1535 	if (success)
1536 	{
1537 		prj->modified = FALSE;
1538 	}
1539 	g_free (contents);
1540 
1541 	return success;
1542 }
1543 
1544 
1545 /** Load a project.
1546  *
1547  * The function loads the project data from file @a filename into the
1548  * project structure @a prj.
1549  *
1550  * @param prj      The project
1551  * @param filename File to load
1552  * @param error    Location to store error info at
1553  * @return TRUE on success, FALSE otherwise
1554  *
1555  **/
wb_project_load(WB_PROJECT * prj,const gchar * filename,GError ** error)1556 gboolean wb_project_load(WB_PROJECT *prj, const gchar *filename, GError **error)
1557 {
1558 	GKeyFile *kf;
1559 	guint	 index;
1560 	gchar	 *contents, *str;
1561 	gchar	 **splitv;
1562 	gchar	 key[100];
1563 	gsize	 length;
1564 	gboolean success = FALSE;
1565 	WB_PROJECT_DIR *new_dir;
1566 
1567 	g_return_val_if_fail(prj, FALSE);
1568 
1569 	if (!g_file_get_contents (filename, &contents, &length, error))
1570 	{
1571 		return FALSE;
1572 	}
1573 
1574 	kf = g_key_file_new ();
1575 
1576 	if (!g_key_file_load_from_data (kf, contents, length,
1577 				G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
1578 				error))
1579 	{
1580 		g_key_file_free (kf);
1581 		g_free (contents);
1582 		return FALSE;
1583 	}
1584 
1585 	/* Import project's base path and file patterns, if not done yet.
1586 	   (from Geany's standard project configuration) */
1587 	if (g_key_file_has_group (kf, "project")
1588 		&& !g_key_file_has_key(kf, "Workbench", "Prj-BaseDir", NULL))
1589 	{
1590 		gchar *base_path;
1591 
1592 		base_path = g_key_file_get_string(kf, "project", "base_path", NULL);
1593 		if (base_path != NULL)
1594 		{
1595 			gchar *reldirname;
1596 
1597 			/* Convert dirname to path relative to the project file */
1598 			reldirname = get_any_relative_path(prj->filename, base_path);
1599 
1600 			new_dir = wb_project_add_directory_int(prj, reldirname, FALSE);
1601 			if (new_dir != NULL)
1602 			{
1603 				wb_project_set_modified(prj, TRUE);
1604 				wb_project_dir_set_is_prj_base_dir(new_dir, TRUE);
1605 				str = g_key_file_get_string(kf, "project", "file_patterns", NULL);
1606 				if (str != NULL)
1607 				{
1608 					splitv = g_strsplit (str, ";", -1);
1609 					wb_project_dir_set_file_patterns(new_dir, splitv);
1610 					g_strfreev(splitv);
1611 				}
1612 				g_free(str);
1613 			}
1614 
1615 			g_free(reldirname);
1616 			g_free(base_path);
1617 		}
1618 	}
1619 
1620 	if (g_key_file_has_group (kf, "Workbench"))
1621 	{
1622 		gchar **bookmarks_strings;
1623 
1624 		/* Load project bookmarks from string list */
1625 		bookmarks_strings = g_key_file_get_string_list (kf, "Workbench", "Bookmarks", NULL, NULL);
1626 		if (bookmarks_strings != NULL)
1627 		{
1628 			gchar **file, *abs_path;
1629 
1630 			file = bookmarks_strings;
1631 			while (*file != NULL)
1632 			{
1633 				abs_path = get_combined_path(prj->filename, *file);
1634 				if (abs_path != NULL)
1635 				{
1636 					wb_project_add_bookmark_int(prj, abs_path);
1637 					g_free(abs_path);
1638 				}
1639 				file++;
1640 			}
1641 			g_strfreev(bookmarks_strings);
1642 		}
1643 
1644 		/* Load project base dir. */
1645 		str = g_key_file_get_string(kf, "Workbench", "Prj-BaseDir", NULL);
1646 		if (str != NULL)
1647 		{
1648 			new_dir = wb_project_add_directory_int(prj, str, FALSE);
1649 			if (new_dir != NULL)
1650 			{
1651 				wb_project_dir_set_is_prj_base_dir(new_dir, TRUE);
1652 
1653 				str = g_key_file_get_string(kf, "Workbench", "Prj-ScanMode", NULL);
1654 				if (g_strcmp0(str, "Git") != 0)
1655 				{
1656 					wb_project_dir_set_scan_mode(prj, new_dir, WB_PROJECT_SCAN_MODE_WORKBENCH);
1657 				}
1658 				else
1659 				{
1660 					wb_project_dir_set_scan_mode(prj, new_dir, WB_PROJECT_SCAN_MODE_GIT);
1661 				}
1662 				g_free(str);
1663 
1664 				str = g_key_file_get_string(kf, "Workbench", "Prj-FilePatterns", NULL);
1665 				if (str != NULL)
1666 				{
1667 					splitv = g_strsplit (str, ";", -1);
1668 					wb_project_dir_set_file_patterns(new_dir, splitv);
1669 				}
1670 				g_free(str);
1671 
1672 				str = g_key_file_get_string(kf, "Workbench", "Prj-IgnoredDirsPatterns", NULL);
1673 				if (str != NULL)
1674 				{
1675 					splitv = g_strsplit (str, ";", -1);
1676 					wb_project_dir_set_ignored_dirs_patterns(new_dir, splitv);
1677 				}
1678 				g_free(str);
1679 
1680 				str = g_key_file_get_string(kf, "Workbench", "Prj-IgnoredFilePatterns", NULL);
1681 				if (str != NULL)
1682 				{
1683 					splitv = g_strsplit (str, ";", -1);
1684 					wb_project_dir_set_ignored_file_patterns(new_dir, splitv);
1685 				}
1686 				g_free(str);
1687 			}
1688 		}
1689 
1690 		/* Load project dirs */
1691 		for (index = 1 ; index < 1025 ; index++)
1692 		{
1693 			g_snprintf(key, sizeof(key), "Dir%u-BaseDir", index);
1694 
1695 			str = g_key_file_get_string(kf, "Workbench", key, NULL);
1696 			if (str == NULL)
1697 			{
1698 				break;
1699 			}
1700 			new_dir = wb_project_add_directory_int(prj, str, FALSE);
1701 			if (new_dir == NULL)
1702 			{
1703 				break;
1704 			}
1705 
1706 			g_snprintf(key, sizeof(key), "Dir%u-ScanMode", index);
1707 			str = g_key_file_get_string(kf, "Workbench", key, NULL);
1708 			if (g_strcmp0(str, "Git") != 0)
1709 			{
1710 				wb_project_dir_set_scan_mode(prj, new_dir, WB_PROJECT_SCAN_MODE_WORKBENCH);
1711 			}
1712 			else
1713 			{
1714 				wb_project_dir_set_scan_mode(prj, new_dir, WB_PROJECT_SCAN_MODE_GIT);
1715 			}
1716 			g_free(str);
1717 
1718 			g_snprintf(key, sizeof(key), "Dir%u-FilePatterns", index);
1719 			str = g_key_file_get_string(kf, "Workbench", key, NULL);
1720 			if (str != NULL)
1721 			{
1722 				splitv = g_strsplit (str, ";", -1);
1723 				wb_project_dir_set_file_patterns(new_dir, splitv);
1724 			}
1725 			g_free(str);
1726 
1727 			g_snprintf(key, sizeof(key), "Dir%u-IgnoredDirsPatterns", index);
1728 			str = g_key_file_get_string(kf, "Workbench", key, NULL);
1729 			if (str != NULL)
1730 			{
1731 				splitv = g_strsplit (str, ";", -1);
1732 				wb_project_dir_set_ignored_dirs_patterns(new_dir, splitv);
1733 			}
1734 			g_free(str);
1735 
1736 			g_snprintf(key, sizeof(key), "Dir%u-IgnoredFilePatterns", index);
1737 			str = g_key_file_get_string(kf, "Workbench", key, NULL);
1738 			if (str != NULL)
1739 			{
1740 				splitv = g_strsplit (str, ";", -1);
1741 				wb_project_dir_set_ignored_file_patterns(new_dir, splitv);
1742 			}
1743 			g_free(str);
1744 		}
1745 	}
1746 
1747 	g_key_file_free(kf);
1748 	g_free (contents);
1749 	success = TRUE;
1750 
1751 	return success;
1752 }
1753 
1754 
1755 /** Create a new empty project.
1756  *
1757  * @return Address of the new structure.
1758  *
1759  **/
wb_project_new(const gchar * filename)1760 WB_PROJECT *wb_project_new(const gchar *filename)
1761 {
1762 	WB_PROJECT *new_prj;
1763 
1764 	new_prj = g_malloc0(sizeof *new_prj);
1765 	new_prj->modified = FALSE;
1766 	wb_project_set_filename(new_prj, filename);
1767 	new_prj->bookmarks = g_ptr_array_new();
1768 	new_prj->generate_tag_prefs = WB_PROJECT_TAG_PREFS_YES;
1769 
1770 	return new_prj;
1771 }
1772 
1773 
1774 /** Free a project.
1775  *
1776  * @param prj Adress of structure to free
1777  *
1778  **/
wb_project_free(WB_PROJECT * prj)1779 void wb_project_free(WB_PROJECT *prj)
1780 {
1781 	/* Free directories first */
1782 	g_slist_free_full(prj->directories, (GDestroyNotify)wb_project_dir_free);
1783 
1784 	/* Free all bookmarks */
1785 	wb_project_free_all_bookmarks(prj);
1786 
1787 	g_free(prj->filename);
1788 	g_free(prj->name);
1789 	g_free(prj);
1790 }
1791 
1792 
1793 /** Check if dir is a valid reference to a directory of prj.
1794  *
1795  * @param prj The project to search in
1796  * @param dir The directory to search for
1797  * @return TRUE  dir is a directory in prj
1798  *         FALSE dir was not found in prj
1799  **/
wb_project_is_valid_dir_reference(WB_PROJECT * prj,WB_PROJECT_DIR * dir)1800 gboolean wb_project_is_valid_dir_reference(WB_PROJECT *prj, WB_PROJECT_DIR *dir)
1801 {
1802 	GSList *elem = NULL;
1803 
1804 	if (prj == NULL)
1805 	{
1806 		return FALSE;
1807 	}
1808 
1809 	foreach_slist(elem, prj->directories)
1810 	{
1811 		if (elem->data == dir)
1812 		{
1813 			return TRUE;
1814 		}
1815 	}
1816 
1817 	return FALSE;
1818 }
1819