1 /*
2  *  geanyprj - Alternative project support for geany light IDE.
3  *
4  *  Copyright 2007 Frank Lanitz <frank(at)frank(dot)uvena(dot)de>
5  *  Copyright 2007 Enrico Tröger <enrico.troeger@uvena.de>
6  *  Copyright 2007 Nick Treleaven <nick.treleaven@btinternet.com>
7  *  Copyright 2007,2008 Yura Siamashka <yurand2@gmail.com>
8  *
9  *  This program is free software: you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation, either version 3 of the License, or
12  *  (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <string.h>
24 #include <sys/time.h>
25 
26 #include "geanyprj.h"
27 
28 const gchar *project_type_string[NEW_PROJECT_TYPE_SIZE] = {
29 	[NEW_PROJECT_TYPE_ALL]    = "All",
30 	[NEW_PROJECT_TYPE_CPP]    = "C/C++",
31 	[NEW_PROJECT_TYPE_C]      = "C",
32 	[NEW_PROJECT_TYPE_PYTHON] = "Python",
33 	[NEW_PROJECT_TYPE_NONE]   = "None"
34 };
35 
project_filter_c_cpp(const gchar * file)36 static gboolean project_filter_c_cpp(const gchar *file)
37 {
38 	if (filetypes_detect_from_file(file)->id == GEANY_FILETYPES_C ||
39 	    filetypes_detect_from_file(file)->id == GEANY_FILETYPES_CPP)
40 		return TRUE;
41 	return FALSE;
42 }
43 
44 
project_filter_c(const gchar * file)45 static gboolean project_filter_c(const gchar *file)
46 {
47 	if (filetypes_detect_from_file(file)->id == GEANY_FILETYPES_C)
48 		return TRUE;
49 	return FALSE;
50 }
51 
52 
project_filter_python(const gchar * file)53 static gboolean project_filter_python(const gchar *file)
54 {
55 	if (filetypes_detect_from_file(file)->id == GEANY_FILETYPES_PYTHON)
56 		return TRUE;
57 	return FALSE;
58 }
59 
60 
project_filter_all(const gchar * file)61 static gboolean project_filter_all(const gchar *file)
62 {
63 	if (filetypes_detect_from_file(file)->id != GEANY_FILETYPES_NONE)
64 		return TRUE;
65 	return FALSE;
66 }
67 
68 
project_filter_none(G_GNUC_UNUSED const gchar * file)69 static gboolean project_filter_none(G_GNUC_UNUSED const gchar *file)
70 {
71 	return FALSE;
72 }
73 
74 
75 gboolean (*project_type_filter[NEW_PROJECT_TYPE_SIZE]) (const gchar *) = {
76 	[NEW_PROJECT_TYPE_ALL]    = project_filter_all,
77 	[NEW_PROJECT_TYPE_CPP]    = project_filter_c_cpp,
78 	[NEW_PROJECT_TYPE_C]      = project_filter_c,
79 	[NEW_PROJECT_TYPE_PYTHON] = project_filter_python,
80 	[NEW_PROJECT_TYPE_NONE]   = project_filter_none
81 };
82 
83 
free_tag_object(gpointer obj)84 static void free_tag_object(gpointer obj)
85 {
86 	if (obj != NULL)
87 	{
88 		tm_workspace_remove_source_file((TMSourceFile *) obj);
89 		tm_source_file_free((TMSourceFile *) obj);
90 	}
91 }
92 
93 
geany_project_new(void)94 struct GeanyPrj *geany_project_new(void)
95 {
96 	struct GeanyPrj *ret;
97 
98 	ret = (struct GeanyPrj *) g_new0(struct GeanyPrj, 1);
99 	ret->tags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_tag_object);
100 
101 	return ret;
102 }
103 
104 
geany_project_load(const gchar * path)105 struct GeanyPrj *geany_project_load(const gchar *path)
106 {
107 	struct GeanyPrj *ret;
108 	TMSourceFile *tm_obj = NULL;
109 	GKeyFile *config;
110 	gint i = 0;
111 	gchar *file;
112 	gchar *filename, *locale_filename;
113 	gchar *key;
114 	gchar *tmp;
115 
116 	debug("%s path=%s\n", __FUNCTION__, path);
117 
118 	if (!path)
119 		return NULL;
120 
121 	config = g_key_file_new();
122 	if (!g_key_file_load_from_file(config, path, G_KEY_FILE_NONE, NULL))
123 	{
124 		g_key_file_free(config);
125 		return NULL;
126 	}
127 
128 
129 	ret = geany_project_new();
130 	geany_project_set_path(ret, path);
131 
132 	tmp = utils_get_setting_string(config, "project", "name", GEANY_STRING_UNTITLED);
133 	geany_project_set_name(ret, tmp);
134 	g_free(tmp);
135 
136 	tmp = utils_get_setting_string(config, "project", "description", "");
137 	geany_project_set_description(ret, tmp);
138 	g_free(tmp);
139 
140 	tmp = utils_get_setting_string(config, "project", "base_path", "");
141 	geany_project_set_base_path(ret, tmp);
142 	g_free(tmp);
143 
144 	tmp = utils_get_setting_string(config, "project", "run_cmd", "");
145 	geany_project_set_run_cmd(ret, tmp);
146 	g_free(tmp);
147 
148 	geany_project_set_type_string(ret,
149 				      utils_get_setting_string(config, "project", "type",
150 							       project_type_string[0]));
151 	geany_project_set_regenerate(ret,
152 				     g_key_file_get_boolean(config, "project", "regenerate", NULL));
153 
154 	if (ret->regenerate)
155 	{
156 		geany_project_regenerate_file_list(ret);
157 	}
158 	else
159 	{
160 		GPtrArray *to_add = g_ptr_array_new();
161 
162 		/* Create tag files */
163 		key = g_strdup_printf("file%d", i);
164 		while ((file = g_key_file_get_string(config, "files", key, NULL)))
165 		{
166 			filename = get_full_path(path, file);
167 
168 			locale_filename = utils_get_locale_from_utf8(filename);
169 			tm_obj = tm_source_file_new(locale_filename,
170 						    filetypes_detect_from_file(filename)->name);
171 			g_free(locale_filename);
172 			if (tm_obj)
173 			{
174 				g_hash_table_insert(ret->tags, filename, tm_obj);
175 				g_ptr_array_add(to_add, tm_obj);
176 			}
177 			else
178 				g_free(filename);
179 			i++;
180 			g_free(key);
181 			g_free(file);
182 			key = g_strdup_printf("file%d", i);
183 		}
184 		tm_workspace_add_source_files(to_add);
185 		g_ptr_array_free(to_add, TRUE);
186 		g_free(key);
187 	}
188 	g_key_file_free(config);
189 	return ret;
190 }
191 
192 
remove_all_tags(struct GeanyPrj * prj)193 static void remove_all_tags(struct GeanyPrj *prj)
194 {
195 	GPtrArray *to_remove, *keys;
196 	GHashTableIter iter;
197 	gpointer key, value;
198 
199 	/* instead of relatively slow removal of source files by one from the tag manager,
200 	 * use the bulk operations - this requires that the normal destroy function
201 	 * of GHashTable is skipped - steal the keys and values and handle them manually */
202 	to_remove = g_ptr_array_new_with_free_func((GFreeFunc)tm_source_file_free);
203 	keys = g_ptr_array_new_with_free_func(g_free);
204 	g_hash_table_iter_init(&iter, prj->tags);
205 	while (g_hash_table_iter_next(&iter, &key, &value))
206 	{
207 		g_ptr_array_add(to_remove, value);
208 		g_ptr_array_add(keys, key);
209 	}
210 	tm_workspace_remove_source_files(to_remove);
211 	g_hash_table_steal_all(prj->tags);
212 	g_ptr_array_free(to_remove, TRUE);
213 	g_ptr_array_free(keys, TRUE);
214 }
215 
216 
geany_project_regenerate_file_list(struct GeanyPrj * prj)217 void geany_project_regenerate_file_list(struct GeanyPrj *prj)
218 {
219 	GSList *lst;
220 
221 	debug("%s path=%s\n", __FUNCTION__, prj->base_path);
222 	remove_all_tags(prj);
223 
224 	lst = get_file_list(prj->base_path, NULL, project_type_filter[prj->type], NULL);
225 	geany_project_set_tags_from_list(prj, lst);
226 
227 	g_slist_foreach(lst, (GFunc) g_free, NULL);
228 	g_slist_free(lst);
229 }
230 
231 
geany_project_set_path(struct GeanyPrj * prj,const gchar * path)232 void geany_project_set_path(struct GeanyPrj *prj, const gchar *path)
233 {
234 	gchar *norm_path = normpath(path);
235 	if (prj->path)
236 	{
237 		if (strcmp(prj->path, norm_path) == 0)
238 		{
239 			g_free(norm_path);
240 			return;
241 		}
242 	}
243 	prj->path = norm_path;
244 }
245 
246 
geany_project_set_name(struct GeanyPrj * prj,const gchar * name)247 void geany_project_set_name(struct GeanyPrj *prj, const gchar *name)
248 {
249 	if (prj->name)
250 		g_free(prj->name);
251 	prj->name = g_strdup(name);
252 }
253 
254 
geany_project_set_type_int(struct GeanyPrj * prj,gint val)255 void geany_project_set_type_int(struct GeanyPrj *prj, gint val)
256 {
257 	prj->type = val;
258 }
259 
260 
geany_project_set_type_string(struct GeanyPrj * prj,const gchar * val)261 void geany_project_set_type_string(struct GeanyPrj *prj, const gchar *val)
262 {
263 	guint i;
264 
265 	for (i = 0; i < sizeof(project_type_string) / sizeof(project_type_string[0]); i++)
266 	{
267 		if (strcmp(val, project_type_string[i]) == 0)
268 		{
269 			geany_project_set_type_int(prj, i);
270 			return;
271 		}
272 	}
273 }
274 
275 
geany_project_set_regenerate(struct GeanyPrj * prj,gboolean val)276 void geany_project_set_regenerate(struct GeanyPrj *prj, gboolean val)
277 {
278 	prj->regenerate = val;
279 }
280 
281 
geany_project_set_description(struct GeanyPrj * prj,const gchar * description)282 void geany_project_set_description(struct GeanyPrj *prj, const gchar *description)
283 {
284 	if (prj->description)
285 		g_free(prj->description);
286 	prj->description = g_strdup(description);
287 }
288 
289 
geany_project_set_base_path(struct GeanyPrj * prj,const gchar * base_path)290 void geany_project_set_base_path(struct GeanyPrj *prj, const gchar *base_path)
291 {
292 	if (prj->base_path)
293 		g_free(prj->base_path);
294 
295 	if (g_path_is_absolute(base_path))
296 	{
297 		prj->base_path = g_strdup(base_path);
298 	}
299 	else
300 	{
301 		prj->base_path = get_full_path(prj->path, base_path);
302 	}
303 }
304 
305 
geany_project_set_run_cmd(struct GeanyPrj * prj,const gchar * run_cmd)306 void geany_project_set_run_cmd(struct GeanyPrj *prj, const gchar *run_cmd)
307 {
308 	if (prj->run_cmd)
309 		g_free(prj->run_cmd);
310 	prj->run_cmd = g_strdup(run_cmd);
311 }
312 
313 
314 /* list in utf8 */
geany_project_set_tags_from_list(struct GeanyPrj * prj,GSList * files)315 void geany_project_set_tags_from_list(struct GeanyPrj *prj, GSList *files)
316 {
317 	GSList *tmp;
318 	gchar *locale_filename;
319 	TMSourceFile *tm_obj = NULL;
320 	GPtrArray *to_add = g_ptr_array_new();
321 
322 	prj->tags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_tag_object);
323 
324 	for (tmp = files; tmp != NULL; tmp = g_slist_next(tmp))
325 	{
326 		locale_filename = utils_get_locale_from_utf8(tmp->data);
327 		tm_obj = tm_source_file_new(locale_filename,
328 					    filetypes_detect_from_file(tmp->data)->name);
329 		g_free(locale_filename);
330 		if (tm_obj)
331 		{
332 			g_hash_table_insert(prj->tags, g_strdup(tmp->data), tm_obj);
333 			g_ptr_array_add(to_add, tm_obj);
334 		}
335 	}
336 	tm_workspace_add_source_files(to_add);
337 	g_ptr_array_free(to_add, TRUE);
338 }
339 
340 
geany_project_free(struct GeanyPrj * prj)341 void geany_project_free(struct GeanyPrj *prj)
342 {
343 	debug("%s prj=%p\n", __FUNCTION__, prj);
344 	g_return_if_fail(prj);
345 
346 	if (prj->path)
347 		g_free(prj->path);
348 	if (prj->name)
349 		g_free(prj->name);
350 	if (prj->description)
351 		g_free(prj->description);
352 	if (prj->base_path)
353 		g_free(prj->base_path);
354 	if (prj->run_cmd)
355 		g_free(prj->run_cmd);
356 	if (prj->tags)
357 	{
358 		remove_all_tags(prj);
359 		g_hash_table_destroy(prj->tags);
360 	}
361 
362 	g_free(prj);
363 }
364 
365 
geany_project_add_file(struct GeanyPrj * prj,const gchar * path)366 gboolean geany_project_add_file(struct GeanyPrj *prj, const gchar *path)
367 {
368 	gchar *filename;
369 	TMSourceFile *tm_obj = NULL;
370 
371 	GKeyFile *config;
372 
373 	config = g_key_file_new();
374 	if (!g_key_file_load_from_file(config, prj->path, G_KEY_FILE_NONE, NULL))
375 	{
376 		g_key_file_free(config);
377 		return FALSE;
378 	}
379 
380 	if (g_hash_table_lookup(prj->tags, path))
381 	{
382 		g_key_file_free(config);
383 		return TRUE;
384 	}
385 	g_key_file_free(config);
386 
387 	filename = utils_get_locale_from_utf8(path);
388 	tm_obj = tm_source_file_new(filename, filetypes_detect_from_file(path)->name);
389 	g_free(filename);
390 	if (tm_obj)
391 	{
392 		g_hash_table_insert(prj->tags, g_strdup(path), tm_obj);
393 		tm_workspace_add_source_file(tm_obj);
394 	}
395 	geany_project_save(prj);
396 	return TRUE;
397 }
398 
399 
400 struct CFGData
401 {
402 	struct GeanyPrj *prj;
403 	GKeyFile *config;
404 	gint i;
405 };
406 
407 
geany_project_save_files(gpointer key,G_GNUC_UNUSED gpointer value,gpointer user_data)408 static void geany_project_save_files(gpointer key, G_GNUC_UNUSED gpointer value, gpointer user_data)
409 {
410 	gchar *fkey;
411 	gchar *filename;
412 	struct CFGData *data = (struct CFGData *) user_data;
413 
414 	filename = get_relative_path(data->prj->path, (const gchar *) key);
415 	if (filename)
416 	{
417 		fkey = g_strdup_printf("file%d", data->i);
418 		g_key_file_set_string(data->config, "files", fkey, filename);
419 		data->i++;
420 		g_free(fkey);
421 		g_free(filename);
422 	}
423 }
424 
425 
geany_project_remove_file(struct GeanyPrj * prj,const gchar * path)426 gboolean geany_project_remove_file(struct GeanyPrj *prj, const gchar *path)
427 {
428 	if (!g_hash_table_remove(prj->tags, path))
429 	{
430 		return FALSE;
431 	}
432 
433 	geany_project_save(prj);
434 	return TRUE;
435 }
436 
437 
geany_project_save(struct GeanyPrj * prj)438 void geany_project_save(struct GeanyPrj *prj)
439 {
440 	GKeyFile *config;
441 	struct CFGData data;
442 	gchar *base_path;
443 
444 	base_path = get_relative_path(prj->path, prj->base_path);
445 
446 	config = g_key_file_new();
447 	g_key_file_load_from_file(config, prj->path, G_KEY_FILE_NONE, NULL);
448 
449 	g_key_file_set_string(config, "project", "name", prj->name);
450 	g_key_file_set_string(config, "project", "description", prj->description);
451 	g_key_file_set_string(config, "project", "base_path", base_path);
452 	g_key_file_set_string(config, "project", "run_cmd", prj->run_cmd);
453 	g_key_file_set_boolean(config, "project", "regenerate", prj->regenerate);
454 	g_key_file_set_string(config, "project", "type", project_type_string[prj->type]);
455 
456 	data.prj = prj;
457 	data.config = config;
458 	data.i = 0;
459 
460 	g_key_file_remove_group(config, "files", NULL);
461 	if (!prj->regenerate)
462 	{
463 		g_hash_table_foreach(prj->tags, geany_project_save_files, &data);
464 	}
465 	save_config(config, prj->path);
466 	g_free(base_path);
467 }
468