1 /*
2  *  Copyright (C) 2008 Marc Pavot <marc.pavot@gmail.com>
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, or (at your option)
7  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  */
19 
20 #include "lib/ario-conf.h"
21 #include <gtk/gtk.h>
22 #include <string.h>
23 #include <config.h>
24 #include <libxml/parser.h>
25 
26 #include "ario-debug.h"
27 #include "ario-util.h"
28 
29 static GHashTable *hash;
30 static gboolean modified = FALSE;
31 static GSList *notifications;
32 static guint notification_counter = 1;
33 
34 typedef struct
35 {
36         guint notification_id;
37         char *key;
38         ArioNotifyFunc notification_callback;
39         gpointer callback_data;
40 } ArioConfNotifyData;
41 
42 #define XML_ROOT_NAME (const unsigned char *)"ario-options"
43 #define XML_VERSION (const unsigned char *)"1.0"
44 
45 static void
ario_conf_free_notify_data(ArioConfNotifyData * data)46 ario_conf_free_notify_data (ArioConfNotifyData *data)
47 {
48         ARIO_LOG_FUNCTION_START;
49         if (data) {
50                 g_free (data->key);
51                 g_free (data);
52         }
53 }
54 
55 static char *
ario_conf_get(const char * key)56 ario_conf_get (const char *key)
57 {
58         ARIO_LOG_FUNCTION_START;
59         return g_hash_table_lookup (hash, key);
60 }
61 
62 static void
ario_conf_set(const char * key,char * value)63 ario_conf_set (const char *key,
64                char *value)
65 {
66         ARIO_LOG_FUNCTION_START;
67         GSList *tmp;
68         ArioConfNotifyData *data;
69 
70         if (!ario_util_strcmp (ario_conf_get (key), value)) {
71                 g_free (value);
72                 return;
73         }
74         g_hash_table_replace (hash, g_strdup (key), value);
75         modified = TRUE;
76 
77         /* Notifications */
78         for (tmp = notifications; tmp; tmp = g_slist_next (tmp)) {
79                 data = tmp->data;
80                 if (!strcmp (data->key, key)) {
81                         data->notification_callback (data->notification_id,
82                                                      data->callback_data);
83                 }
84         }
85 }
86 
87 void
ario_conf_set_boolean(const char * key,gboolean boolean_value)88 ario_conf_set_boolean (const char *key,
89                        gboolean boolean_value)
90 {
91         ARIO_LOG_FUNCTION_START;
92         if (boolean_value)
93                 ario_conf_set (key, g_strdup ("1"));
94         else
95                 ario_conf_set (key, g_strdup ("0"));
96 }
97 
98 gboolean
ario_conf_get_boolean(const char * key,const gboolean default_value)99 ario_conf_get_boolean (const char *key,
100                        const gboolean default_value)
101 {
102         ARIO_LOG_FUNCTION_START;
103         gchar *value = ario_conf_get (key);
104         gboolean ret;
105 
106         if (!value)
107                 return default_value;
108 
109         ret = !strcmp (value, "1");
110 
111         return ret;
112 }
113 
114 void
ario_conf_set_integer(const char * key,int int_value)115 ario_conf_set_integer (const char *key,
116                        int int_value)
117 {
118         ARIO_LOG_FUNCTION_START;
119         ario_conf_set (key, g_strdup_printf ("%d", int_value));
120 }
121 
122 int
ario_conf_get_integer(const char * key,const int default_value)123 ario_conf_get_integer (const char *key,
124                        const int default_value)
125 {
126         ARIO_LOG_FUNCTION_START;
127         gchar *value = ario_conf_get (key);
128         int ret;
129 
130         if (!value)
131                 return default_value;
132 
133         ret = atoi (value);
134 
135         return ret;
136 }
137 
138 void
ario_conf_set_float(const char * key,gfloat float_value)139 ario_conf_set_float (const char *key,
140                      gfloat float_value)
141 {
142         ARIO_LOG_FUNCTION_START;
143         ario_conf_set (key, g_strdup_printf ("%f", float_value));
144 }
145 
146 gfloat
ario_conf_get_float(const char * key,const gfloat default_value)147 ario_conf_get_float (const char *key,
148                      const gfloat default_value)
149 {
150         ARIO_LOG_FUNCTION_START;
151         gchar *value = ario_conf_get (key);
152         gfloat ret;
153 
154         if (!value)
155                 return default_value;
156 
157         ret = atof (value);
158         return ret;
159 }
160 
161 void
ario_conf_set_string(const char * key,const char * string_value)162 ario_conf_set_string (const char *key,
163                       const char *string_value)
164 {
165         ARIO_LOG_FUNCTION_START;
166         ario_conf_set (key, g_strdup (string_value));
167 }
168 
169 const char *
ario_conf_get_string(const char * key,const char * default_value)170 ario_conf_get_string (const char *key,
171                       const char *default_value)
172 {
173         ARIO_LOG_FUNCTION_START;
174         gchar *value = ario_conf_get (key);
175 
176         if (!value)
177                 return default_value;
178 
179         return value;
180 }
181 
182 void
ario_conf_set_string_slist(const char * key,const GSList * slist)183 ario_conf_set_string_slist (const char *key,
184                             const GSList *slist)
185 {
186         ARIO_LOG_FUNCTION_START;
187         GString* value = NULL;
188         const GSList *tmp;
189         gboolean first = TRUE;
190 
191         value = g_string_new("");
192 
193         for (tmp = slist; tmp; tmp = g_slist_next (tmp)) {
194                 if (!first) {
195                         g_string_append (value, ",");
196                 }
197                 g_string_append (value, tmp->data);
198                 first = FALSE;
199         }
200 
201         ario_conf_set (key, value->str);
202 
203         g_string_free (value, FALSE);
204 }
205 
206 GSList *
ario_conf_get_string_slist(const char * key,const char * string_value)207 ario_conf_get_string_slist (const char *key,
208                             const char *string_value)
209 {
210         ARIO_LOG_FUNCTION_START;
211         const gchar *value = ario_conf_get (key);
212         gchar **values;
213         GSList *ret = NULL;
214         int i;
215 
216         if (!value)
217                 value = string_value;
218 
219         if (!value)
220                 return NULL;
221 
222         values = g_strsplit ((const gchar *) value, ",", 0);
223 
224         for (i=0; values[i]!=NULL && g_utf8_collate (values[i], ""); ++i)
225                 ret = g_slist_append (ret, values[i]);
226 
227         g_free (values);
228 
229         return ret;
230 }
231 
232 static gboolean
ario_conf_save_foreach(gchar * key,gchar * value,xmlNodePtr root)233 ario_conf_save_foreach (gchar *key,
234                         gchar *value,
235                         xmlNodePtr root)
236 {
237         ARIO_LOG_FUNCTION_START;
238         xmlNodePtr cur;
239 
240         /* We add a new "option" entry */
241         cur = xmlNewChild (root, NULL, (const xmlChar *) "option", NULL);
242         xmlSetProp (cur, (const xmlChar *) "key", (const xmlChar *) key);
243         xmlNodeAddContent (cur, (const xmlChar *) value);
244 
245         return FALSE;
246 }
247 
248 static gint
ario_conf_compare_keys(gconstpointer a,gconstpointer b)249 ario_conf_compare_keys (gconstpointer a,
250                         gconstpointer b)
251 {
252         const xmlChar * str1 = (const xmlChar *) a;
253         const xmlChar * str2 = (const xmlChar *) b;
254         return xmlStrcmp (str1, str2);
255 }
256 
257 static void
ario_conf_sorted_save_foreach(gchar * key,gchar * value,GTree * sorted_pairs)258 ario_conf_sorted_save_foreach (gchar *key,
259                                gchar *value,
260                                GTree *sorted_pairs)
261 {
262         g_tree_insert(sorted_pairs, key, value);
263 }
264 
265 static gboolean
ario_conf_save(G_GNUC_UNUSED gpointer data)266 ario_conf_save (G_GNUC_UNUSED gpointer data)
267 {
268         ARIO_LOG_FUNCTION_START;
269         xmlNodePtr cur;
270         xmlDocPtr doc;
271         char *xml_filename;
272         GTree *sorted_pairs;
273 
274         if (!modified)
275                 return TRUE;
276         modified = FALSE;
277 
278         doc = xmlNewDoc (XML_VERSION);
279         cur = xmlNewNode (NULL, (const xmlChar *) XML_ROOT_NAME);
280         xmlDocSetRootElement (doc, cur);
281 
282         /* We sort the keys before saving to avoid changing the
283            configuration file if only the order changes */
284         sorted_pairs = g_tree_new(ario_conf_compare_keys);
285         g_hash_table_foreach (hash,
286                               (GHFunc) ario_conf_sorted_save_foreach,
287                               sorted_pairs);
288         g_tree_foreach (sorted_pairs,
289                         (GTraverseFunc) ario_conf_save_foreach,
290                         cur);
291         g_tree_destroy (sorted_pairs);
292 
293         xml_filename = g_build_filename (ario_util_config_dir (), "options.xml", NULL);
294 
295         /* We save the xml file */
296         xmlSaveFormatFile (xml_filename, doc, TRUE);
297 
298         g_free (xml_filename);
299         xmlFreeDoc (doc);
300 
301         return TRUE;
302 }
303 
304 void
ario_conf_init(void)305 ario_conf_init (void)
306 {
307         ARIO_LOG_FUNCTION_START;
308         xmlNodePtr cur;
309         xmlDocPtr doc;
310         xmlChar *xml_key;
311         xmlChar *xml_value;
312         char *xml_filename;
313 
314         xml_filename = g_build_filename (ario_util_config_dir (), "options.xml", NULL);
315 
316         /* This option is necessary to save a well formated xml file */
317         xmlKeepBlanksDefault (0);
318 
319         hash = g_hash_table_new_full (g_str_hash, g_str_equal,
320                                       g_free, g_free);
321 
322         if (ario_util_uri_exists (xml_filename)) {
323                 doc = xmlParseFile (xml_filename);
324                 g_free (xml_filename);
325 
326                 cur = xmlDocGetRootElement(doc);
327                 if (cur == NULL)
328                         return;
329 
330                 for (cur = cur->children; cur; cur = cur->next) {
331                         /* For each "option" entry */
332                         if (!xmlStrcmp (cur->name, (const xmlChar *) "option")) {
333                                 xml_key = xmlGetProp (cur, (const unsigned char *) "key");
334                                 xml_value = xmlNodeGetContent (cur);
335                                 g_hash_table_insert (hash, xml_key, xml_value);
336                         }
337                 }
338 
339                 xmlFreeDoc (doc);
340         }
341 
342         g_timeout_add (5*1000, (GSourceFunc) ario_conf_save, NULL);
343 }
344 
345 void
ario_conf_shutdown(void)346 ario_conf_shutdown (void)
347 {
348         ARIO_LOG_FUNCTION_START;
349         ario_conf_save (NULL);
350         g_hash_table_remove_all (hash);
351         g_slist_foreach (notifications, (GFunc) ario_conf_free_notify_data, NULL);
352 }
353 
354 guint
ario_conf_notification_add(const char * key,ArioNotifyFunc notification_callback,gpointer callback_data)355 ario_conf_notification_add (const char *key,
356                             ArioNotifyFunc notification_callback,
357                             gpointer callback_data)
358 {
359         ARIO_LOG_FUNCTION_START;
360         ArioConfNotifyData *data = (ArioConfNotifyData *) g_malloc0 (sizeof (ArioConfNotifyData));
361         ++notification_counter;
362 
363         data->notification_id = notification_counter;
364         data->key = g_strdup (key);
365         data->notification_callback = notification_callback;
366         data->callback_data = callback_data;
367 
368         notifications = g_slist_append (notifications, data);
369 
370         return notification_counter;
371 }
372 
373 void
ario_conf_notification_remove(guint notification_id)374 ario_conf_notification_remove (guint notification_id)
375 {
376         ARIO_LOG_FUNCTION_START;
377         GSList *tmp;
378         ArioConfNotifyData *data;
379 
380         for (tmp = notifications; tmp; tmp = g_slist_next (tmp)) {
381                 data = tmp->data;
382                 if (data->notification_id == notification_id) {
383                         notifications = g_slist_remove (notifications, data);
384                         ario_conf_free_notify_data (data);
385                 }
386         }
387 }
388 
389