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, ¶ms);
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, ¶ms);
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