Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / libedataserver / e-categories.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* 
3  * Copyright (C) 2005 Novell, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
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 GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include <string.h>
25 #include <libxml/parser.h>
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gconf/gconf-client.h>
29 #include "e-categories.h"
30
31 #include "libedataserver-private.h"
32
33 typedef struct {
34         char *category;
35         char *icon_file;
36         char *color;
37         gboolean searchable;
38 } CategoryInfo;
39
40 static CategoryInfo default_categories[] = {
41         { N_("Anniversary") },
42         { N_("Birthday"), "category_birthday_16.png" },
43         { N_("Business"), "category_business_16.png" },
44         { N_("Competition") },
45         { N_("Favorites"), "category_favorites_16.png" },
46         { N_("Gifts"), "category_gifts_16.png" },
47         { N_("Goals/Objectives"), "category_goals_16.png" },
48         { N_("Holiday"), "category_holiday_16.png" },
49         { N_("Holiday Cards"), "category_holiday-cards_16.png" },
50         /* important people (e.g. new business partners) */
51         { N_("Hot Contacts"), "category_hot-contacts_16.png" },
52         { N_("Ideas"), "category_ideas_16.png" },
53         { N_("International"), "category_international_16.png" },
54         { N_("Key Customer"), "category_key-customer_16.png" },
55         { N_("Miscellaneous"), "category_miscellaneous_16.png" },
56         { N_("Personal"), "category_personal_16.png" },
57         { N_("Phone Calls"), "category_phonecalls_16.png" },
58         { N_("Status"), "category_status_16.png" },
59         { N_("Strategies"), "category_strategies_16.png" },
60         { N_("Suppliers"), "category_suppliers_16.png" },
61         { N_("Time & Expenses"), "category_time-and-expenses_16.png" },
62         { N_("VIP") },
63         { N_("Waiting") },
64         { NULL }
65 };
66
67 static gboolean initialized = FALSE;
68 static GHashTable *categories_table = NULL;
69 static gboolean save_is_pending = FALSE;
70 static guint idle_id = 0;
71
72 static gchar *
73 build_categories_filename (void)
74 {
75         return g_build_filename (g_get_home_dir (),
76                 ".evolution", "categories.xml", NULL);
77 }
78
79 static void
80 free_category_info (CategoryInfo *cat_info)
81 {
82         g_free (cat_info->category);
83         g_free (cat_info->icon_file);
84         g_free (cat_info->color);
85         g_free (cat_info);
86 }
87
88 static gchar *
89 escape_string (const gchar *source)
90 {
91         GString *buffer;
92
93         buffer = g_string_sized_new (strlen (source));
94
95         while (*source) {
96                 switch (*source) {
97                 case '<':
98                         g_string_append_len (buffer, "&lt;", 4);
99                         break;
100                 case '>':
101                         g_string_append_len (buffer, "&gt;", 4);
102                         break;
103                 case '&':
104                         g_string_append_len (buffer, "&amp;", 5);
105                         break;
106                 case '"':
107                         g_string_append_len (buffer, "&quot;", 6);
108                         break;
109                 default:
110                         g_string_append_c (buffer, *source);
111                         break;
112                 }
113                 source++;
114         }
115
116         return g_string_free (buffer, FALSE);
117 }
118
119 static void
120 hash_to_xml_string (gpointer key, gpointer value, gpointer user_data)
121 {
122         CategoryInfo *cat_info = value;
123         GString *string = user_data;
124         gchar *category;
125
126         g_string_append_len (string, "\t<category", 10);
127
128         category = escape_string (cat_info->category);
129         g_string_append_printf (string, " a=\"%s\"", category);
130         g_free (category);
131
132         if (cat_info->icon_file != NULL)
133                 g_string_append_printf (
134                         string, " icon=\"%s\"", cat_info->icon_file);
135
136         if (cat_info->color != NULL)
137                 g_string_append_printf (
138                         string, " color=\"%s\"", cat_info->color);
139
140         g_string_append_printf (
141                 string, " searchable=\"%d\"", cat_info->searchable);
142
143         g_string_append_len (string, "/>\n", 3);
144 }
145
146 static gboolean
147 idle_saver_cb (gpointer user_data)
148 {
149         GString *buffer;
150         gchar *contents;
151         gchar *filename;
152         GError *error = NULL;
153
154         if (!save_is_pending)
155                 goto exit;
156
157         filename = build_categories_filename ();
158
159         g_debug ("Saving categories to \"%s\"", filename);
160
161         /* build the file contents */
162         buffer = g_string_new ("<categories>\n");
163         g_hash_table_foreach (categories_table, hash_to_xml_string, buffer);
164         g_string_append_len (buffer, "</categories>\n", 14);
165         contents = g_string_free (buffer, FALSE);
166
167         if (!g_file_set_contents (filename, contents, -1, &error)) {
168                 g_warning ("Unable to save categories: %s", error->message);
169                 g_error_free (error);
170         }
171
172         g_free (contents);
173         g_free (filename);
174         save_is_pending = FALSE;
175
176 exit:
177         idle_id = 0;
178         return FALSE;
179 }
180
181 static void
182 save_categories (void)
183 {
184         save_is_pending = TRUE;
185
186         if (idle_id == 0)
187                 idle_id = g_idle_add (idle_saver_cb, NULL);
188 }
189
190 static gint
191 parse_categories (const gchar *contents, gsize length)
192 {
193         xmlDocPtr doc;
194         xmlNodePtr node;
195         gint n_added = 0;
196
197         doc = xmlParseMemory (contents, length);
198         if (doc == NULL) {
199                 g_warning ("Unable to parse categories");
200                 return 0;
201         }
202
203         node = xmlDocGetRootElement (doc);
204         if (node == NULL) {
205                 g_warning ("Unable to parse categories");
206                 xmlFreeDoc (doc);
207                 return 0;
208         }
209
210         for (node = node->xmlChildrenNode; node != NULL; node = node->next) {
211                 xmlChar *category, *icon, *color, *searchable;
212
213                 category = xmlGetProp (node, (xmlChar *) "a");
214                 icon = xmlGetProp (node, (xmlChar *) "icon");
215                 color = xmlGetProp (node, (xmlChar *) "color");
216                 searchable = xmlGetProp (node, (xmlChar *) "searchable");
217
218                 if (category != NULL) {
219                         e_categories_add (
220                                 (gchar *) category,
221                                 (gchar *) color,
222                                 (gchar *) icon,
223                                 (searchable != NULL) &&
224                                 strcmp ((gchar *) searchable, "0") != 0);
225                         n_added++;
226                 }
227
228                 xmlFree (category);
229                 xmlFree (icon);
230                 xmlFree (color);
231                 xmlFree (searchable);
232         }
233
234         xmlFreeDoc (doc);
235
236         return n_added;
237 }
238
239 static gint
240 load_categories (void)
241 {
242         gchar *contents;
243         gchar *filename;
244         gsize length;
245         gint n_added = 0;
246         GError *error = NULL;
247
248         contents = NULL;
249         filename = build_categories_filename ();
250
251         if (!g_file_test (filename, G_FILE_TEST_EXISTS))
252                 goto exit;
253
254         g_debug ("Loading categories from \"%s\"", filename);
255
256         if (!g_file_get_contents (filename, &contents, &length, &error)) {
257                 g_warning ("Unable to load categories: %s", error->message);
258                 g_error_free (error);
259                 goto exit;
260         }
261
262         n_added = parse_categories (contents, length);
263
264 exit:
265         g_free (contents);
266         g_free (filename);
267
268         return n_added;
269 }
270
271 static void
272 migrate_old_icon_file (gpointer key, gpointer value, gpointer user_data)
273 {
274         CategoryInfo *info = value;
275         gchar *basename;
276
277         if (info->icon_file == NULL)
278                 return;
279
280         /* We can't be sure where the old icon files were stored, but
281          * a good guess is (E_DATA_SERVER_IMAGESDIR "-2.x").  Convert
282          * any such paths to just E_DATA_SERVER_IMAGESDIR. */
283         if (g_str_has_prefix (info->icon_file, E_DATA_SERVER_IMAGESDIR)) {
284                 basename = g_path_get_basename (info->icon_file);
285                 g_free (info->icon_file);
286                 info->icon_file = g_build_filename (
287                         E_DATA_SERVER_IMAGESDIR, basename, NULL);
288                 g_free (basename);
289         }
290 }
291
292 static gboolean
293 migrate_old_categories (void)
294 {
295         /* Try migrating old category settings from GConf to the new
296          * category XML file.  If successful, unset the old GConf key
297          * so that this is a one-time-only operation. */
298
299         const gchar *key = "/apps/evolution/general/category_master_list";
300
301         GConfClient *client;
302         gchar *string;
303         gint n_added = 0;
304
305         client = gconf_client_get_default ();
306         string = gconf_client_get_string (client, key, NULL);
307         if (string == NULL || *string == '\0')
308                 goto exit;
309
310         g_debug ("Loading categories from GConf key \"%s\"", key);
311
312         n_added = parse_categories (string, strlen (string));
313         if (n_added == 0)
314                 goto exit;
315
316         /* default icon files are now in an unversioned directory */
317         g_hash_table_foreach (categories_table, migrate_old_icon_file, NULL);
318
319         gconf_client_unset (client, key, NULL);
320
321 exit:
322         g_object_unref (client);
323         g_free (string);
324
325         return n_added;
326 }
327
328 static void
329 load_default_categories (void)
330 {
331         CategoryInfo *cat_info = default_categories;
332
333         /* Note: All default categories are searchable. */
334         while (cat_info->category != NULL) {
335                 if (cat_info->icon_file != NULL)
336                         cat_info->icon_file = g_build_filename (E_DATA_SERVER_IMAGESDIR, cat_info->icon_file, NULL);
337                 e_categories_add (
338                         gettext (cat_info->category),
339                         NULL, cat_info->icon_file, TRUE);
340                 g_free (cat_info->icon_file);
341                 cat_info++;
342         }
343 }
344
345 static void
346 finalize_categories (void)
347 {
348         if (save_is_pending)
349                 idle_saver_cb (NULL);
350
351         if (idle_id > 0) {
352                 g_source_remove (idle_id);
353                 idle_id = 0;
354         }
355
356         if (categories_table != NULL) {
357                 g_hash_table_destroy (categories_table);
358                 categories_table = NULL;
359         }
360
361         initialized = FALSE;
362 }
363
364 static void
365 initialize_categories (void)
366 {
367         gint n_added;
368
369         if (initialized)
370                 return;
371
372         initialized = TRUE;
373
374         categories_table = g_hash_table_new_full (
375                 g_str_hash, g_str_equal, g_free,
376                 (GDestroyNotify) free_category_info);
377
378         g_atexit (finalize_categories);
379
380         n_added = load_categories ();
381         if (n_added > 0) {
382                 g_debug ("Loaded %d categories", n_added);
383                 save_is_pending = FALSE;
384                 return;
385         }
386
387         n_added = migrate_old_categories ();
388         if (n_added > 0) {
389                 g_debug ("Loaded %d categories", n_added);
390                 save_categories ();
391                 return;
392         }
393
394         load_default_categories ();
395         g_debug ("Loaded default categories");
396         save_categories ();
397 }
398
399 static void
400 add_hash_to_list (gpointer key, gpointer value, gpointer user_data)
401 {
402         GList **list = user_data;
403
404         *list = g_list_prepend (*list, key);
405 }
406
407 /**
408  * e_categories_get_list:
409  *
410  * Returns a sorted list of all the category names currently configured.
411  *
412  * Return value: a sorted GList containing the names of the categories. The
413  * list should be freed using g_list_free, but the names of the categories
414  * should not be touched at all, they are internal strings.
415  */
416 GList *
417 e_categories_get_list (void)
418 {
419         GList *list = NULL;
420
421         if (!initialized)
422                 initialize_categories ();
423
424         g_hash_table_foreach (categories_table, add_hash_to_list, &list);
425
426         return g_list_sort (list, (GCompareFunc) g_utf8_collate);
427 }
428
429 /**
430  * e_categories_add:
431  * @category: name of category to add.
432  * @color: associated color.
433  * @icon_file: full path of the icon associated to the category.
434  * @searchable: whether the category can be used for searching in the GUI.
435  *
436  * Adds a new category, with its corresponding color and icon, to the
437  * configuration database.
438  */
439 void
440 e_categories_add (const char *category, const char *color, const char *icon_file, gboolean searchable)
441 {
442         CategoryInfo *cat_info;
443
444         g_return_if_fail (category != NULL);
445
446         if (!initialized)
447                 initialize_categories ();
448
449         /* add the new category */
450         cat_info = g_new0 (CategoryInfo, 1);
451         cat_info->category = g_strdup (category);
452         cat_info->color = g_strdup (color);
453         cat_info->icon_file = g_strdup (icon_file);
454         cat_info->searchable = searchable;
455
456         g_hash_table_insert (categories_table, g_strdup (category), cat_info);
457
458         save_categories ();
459 }
460
461 /**
462  * e_categories_remove:
463  * @category: category to be removed.
464  *
465  * Removes the given category from the configuration.
466  */
467 void
468 e_categories_remove (const char *category)
469 {
470         g_return_if_fail (category != NULL);
471
472         if (!initialized)
473                 initialize_categories ();
474
475         if (g_hash_table_remove (categories_table, category))
476                 save_categories ();
477 }
478
479 /**
480  * e_categories_exist:
481  * @category: category to be searched.
482  *
483  * Checks whether the given category is available in the configuration.
484  *
485  * Return value: %TRUE if the category is available, %FALSE otherwise.
486  */
487 gboolean
488 e_categories_exist (const char *category)
489 {
490         g_return_val_if_fail (category != NULL, FALSE);
491
492         if (!initialized)
493                 initialize_categories ();
494
495         return (g_hash_table_lookup (categories_table, category) != NULL);
496 }
497
498 /**
499  * e_categories_get_color_for:
500  * @category: category to retrieve the color for.
501  *
502  * Gets the color associated with the given category.
503  *
504  * Return value: a string representation of the color.
505  */
506 const char *
507 e_categories_get_color_for (const char *category)
508 {
509         CategoryInfo *cat_info;
510
511         g_return_val_if_fail (category != NULL, NULL);
512
513         if (!initialized)
514                 initialize_categories ();
515
516         cat_info = g_hash_table_lookup (categories_table, category);
517         if (cat_info == NULL)
518                 return NULL;
519
520         return cat_info->color;
521 }
522
523 /**
524  * e_categories_set_color_for:
525  * @category: category to set the color for.
526  * @color: X color.
527  *
528  * Sets the color associated with the given category.
529  */
530 void
531 e_categories_set_color_for (const char *category, const char *color)
532 {
533         CategoryInfo *cat_info;
534
535         g_return_if_fail (category != NULL);
536
537         if (!initialized)
538                 initialize_categories ();
539
540         cat_info = g_hash_table_lookup (categories_table, category);
541         g_return_if_fail (cat_info != NULL);
542
543         g_free (cat_info->color);
544         cat_info->color = g_strdup (color);
545         save_categories ();
546 }
547
548 /**
549  * e_categories_get_icon_file_for:
550  * @category: category to retrieve the icon file for.
551  *
552  * Gets the icon file associated with the given category.
553  *
554  * Return value: a string representation of the color.
555  */
556 const char *
557 e_categories_get_icon_file_for (const char *category)
558 {
559         CategoryInfo *cat_info;
560
561         g_return_val_if_fail (category != NULL, NULL);
562
563         if (!initialized)
564                 initialize_categories ();
565
566         cat_info = g_hash_table_lookup (categories_table, category);
567         if (cat_info == NULL)
568                 return NULL;
569
570         return cat_info->icon_file;
571 }
572
573 /**
574  * e_categories_set_icon_file_for:
575  * @category: category to set the icon file for.
576  * @color: X color.
577  *
578  * Sets the icon file associated with the given category.
579  */
580 void
581 e_categories_set_icon_file_for (const char *category, const char *icon_file)
582 {
583         CategoryInfo *cat_info;
584
585         g_return_if_fail (category != NULL);
586
587         if (!initialized)
588                 initialize_categories ();
589
590         cat_info = g_hash_table_lookup (categories_table, category);
591         g_return_if_fail (cat_info != NULL);
592
593         g_free (cat_info->icon_file);
594         cat_info->icon_file = g_strdup (icon_file);
595         save_categories ();
596 }
597
598 /**
599  * e_categories_is_searchable:
600  * @category: category name.
601  *
602  * Gets whether the given calendar is to be used for searches in the GUI.
603  *
604  * Return value; %TRUE% if the category is searchable, %FALSE% if not.
605  */
606 gboolean
607 e_categories_is_searchable (const char *category)
608 {
609         CategoryInfo *cat_info;
610
611         g_return_val_if_fail (category != NULL, FALSE);
612
613         if (!initialized)
614                 initialize_categories ();
615
616         cat_info = g_hash_table_lookup (categories_table, category);
617         if (cat_info == NULL)
618                 return FALSE;
619
620         return cat_info->searchable;
621 }