Fixes: NB#162670 - test crashing in scratchbox
[accounts-sso:accounts-glib.git] / libaccounts-glib / ag-service.c
1 /* vi: set et sw=4 ts=4 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /*
4  * This file is part of libaccounts-glib
5  *
6  * Copyright (C) 2009-2010 Nokia Corporation.
7  *
8  * Contact: Alberto Mardegan <alberto.mardegan@nokia.com>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * version 2.1 as published by the Free Software Foundation.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22  * 02110-1301 USA
23  */
24
25 /**
26  * SECTION:ag-service
27  * @title: AgService
28  * @short_description: A representation of a service.
29  *
30  * The #AgService structure represents a service. The structure is not directly
31  * exposed to applications, but its fields are accessible via getter methods.
32  * The structure is reference counted. One must use ag_service_unref() when
33  * done with it.
34  */
35
36 #include "config.h"
37 #include "ag-service.h"
38
39 #include "ag-internals.h"
40 #include "ag-util.h"
41 #include <libxml/xmlreader.h>
42 #include <string.h>
43
44 static gint
45 cmp_service_name (AgService *service, const gchar *service_name)
46 {
47     const gchar *name;
48
49     name = ag_service_get_name (service);
50     if (G_UNLIKELY (!name)) return 1;
51
52     return strcmp (name, service_name);
53 }
54
55 static void
56 add_services_from_dir (AgManager *manager, const gchar *dirname,
57                        GList **services)
58 {
59     const gchar *filename, *suffix;
60     gchar service_name[256];
61     AgService *service;
62     GDir *dir;
63
64     g_return_if_fail (services != NULL);
65     g_return_if_fail (dirname != NULL);
66
67     dir = g_dir_open (dirname, 0, NULL);
68     if (!dir) return;
69
70     while ((filename = g_dir_read_name (dir)) != NULL)
71     {
72         if (filename[0] == '.')
73             continue;
74
75         suffix = strstr (filename, ".service");
76         if (!suffix) continue;
77
78         g_snprintf (service_name, sizeof (service_name),
79                     "%.*s", suffix - filename, filename);
80
81         /* if there is already a service with the same name in the list, then
82          * we skip this one (we process directories in descending order of
83          * priority) */
84         if (g_list_find_custom (*services, service_name,
85                                 (GCompareFunc)cmp_service_name))
86             continue;
87
88         service = ag_manager_get_service (manager, service_name);
89         if (G_UNLIKELY (!service)) continue;
90
91         *services = g_list_prepend (*services, service);
92     }
93
94     g_dir_close (dir);
95 }
96
97 GList *
98 _ag_services_list (AgManager *manager)
99 {
100     const gchar * const *dirs;
101     const gchar *env_dirname, *datadir;
102     gchar *dirname;
103     GList *services = NULL;
104
105     env_dirname = g_getenv ("AG_SERVICES");
106     if (env_dirname)
107     {
108         add_services_from_dir (manager, env_dirname, &services);
109         /* If the environment variable is set, don't look in other places */
110         return services;
111     }
112
113     datadir = g_get_user_data_dir ();
114     if (G_LIKELY (datadir))
115     {
116         dirname = g_build_filename (datadir, SERVICE_FILES_DIR, NULL);
117         add_services_from_dir (manager, dirname, &services);
118         g_free (dirname);
119     }
120
121     dirs = g_get_system_data_dirs ();
122     for (datadir = *dirs; datadir != NULL; dirs++, datadir = *dirs)
123     {
124         dirname = g_build_filename (datadir, SERVICE_FILES_DIR, NULL);
125         add_services_from_dir (manager, dirname, &services);
126         g_free (dirname);
127     }
128     return services;
129 }
130
131 static gboolean
132 parse_template (xmlTextReaderPtr reader, AgService *service)
133 {
134     GHashTable *settings;
135     gboolean ok;
136
137     g_return_val_if_fail (service->default_settings == NULL, FALSE);
138
139     settings =
140         g_hash_table_new_full (g_str_hash, g_str_equal,
141                                g_free, (GDestroyNotify)_ag_value_slice_free);
142
143     ok = _ag_xml_parse_settings (reader, "", settings);
144     if (G_UNLIKELY (!ok))
145     {
146         g_hash_table_destroy (settings);
147         return FALSE;
148     }
149
150     service->default_settings = settings;
151     return TRUE;
152 }
153
154 static gboolean
155 parse_preview (xmlTextReaderPtr reader, AgService *service)
156 {
157     /* TODO: implement */
158     return TRUE;
159 }
160
161 static gboolean
162 parse_service (xmlTextReaderPtr reader, AgService *service)
163 {
164     const gchar *name;
165     int ret, type;
166
167     if (!service->name)
168     {
169         service->name = g_strdup
170             ((const gchar *)xmlTextReaderGetAttribute (reader,
171                                                        (xmlChar *) "id"));
172     }
173
174     ret = xmlTextReaderRead (reader);
175     while (ret == 1)
176     {
177         name = (const gchar *)xmlTextReaderConstName (reader);
178         if (G_UNLIKELY (!name)) return FALSE;
179
180         type = xmlTextReaderNodeType (reader);
181         if (type == XML_READER_TYPE_END_ELEMENT &&
182             strcmp (name, "service") == 0)
183             break;
184
185         if (type == XML_READER_TYPE_ELEMENT)
186         {
187             gboolean ok;
188
189             if (strcmp (name, "type") == 0 && !service->type)
190             {
191                 ok = _ag_xml_dup_element_data (reader, &service->type);
192             }
193             else if (strcmp (name, "name") == 0 && !service->display_name)
194             {
195                 ok = _ag_xml_dup_element_data (reader, &service->display_name);
196             }
197             else if (strcmp (name, "provider") == 0 && !service->provider)
198             {
199                 ok = _ag_xml_dup_element_data (reader, &service->provider);
200             }
201             else if (strcmp (name, "icon") == 0)
202             {
203                 ok = _ag_xml_dup_element_data (reader, &service->icon_name);
204             }
205             else if (strcmp (name, "template") == 0)
206             {
207                 ok = parse_template (reader, service);
208             }
209             else if (strcmp (name, "preview") == 0)
210             {
211                 ok = parse_preview (reader, service);
212             }
213             else if (strcmp (name, "type_data") == 0)
214             {
215                 static const gchar *element = "<type_data";
216                 gsize offset;
217
218                 /* find the offset in the file where this element begins */
219                 offset = xmlTextReaderByteConsumed(reader);
220                 while (offset > 0)
221                 {
222                     if (strncmp (service->file_data + offset, element,
223                                  sizeof (element)) == 0)
224                     {
225                         service->type_data_offset = offset;
226                         break;
227                     }
228                     offset--;
229                 }
230
231                 /* this element is placed after all the elements we are
232                  * interested in: we can stop the parsing now */
233                 return TRUE;
234             }
235             else
236                 ok = TRUE;
237
238             if (G_UNLIKELY (!ok)) return FALSE;
239         }
240
241         ret = xmlTextReaderNext (reader);
242     }
243     return TRUE;
244 }
245
246 static gboolean
247 read_service_file (xmlTextReaderPtr reader, AgService *service)
248 {
249     const xmlChar *name;
250     int ret;
251
252     ret = xmlTextReaderRead (reader);
253     while (ret == 1)
254     {
255         name = xmlTextReaderConstName (reader);
256         if (G_LIKELY (name &&
257                       strcmp ((const gchar *)name, "service") == 0))
258         {
259             return parse_service (reader, service);
260         }
261
262         ret = xmlTextReaderNext (reader);
263     }
264     return FALSE;
265 }
266
267 static gchar *
268 find_service_file (const gchar *service_id)
269 {
270     const gchar * const *dirs;
271     const gchar *dirname;
272     const gchar *env_dirname;
273     gchar *filename, *filepath;
274
275     filename = g_strdup_printf ("%s.service", service_id);
276     env_dirname = g_getenv ("AG_SERVICES");
277     if (env_dirname)
278     {
279         filepath = g_build_filename (env_dirname, filename, NULL);
280         if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
281             goto found;
282         g_free (filepath);
283     }
284
285     dirname = g_get_user_data_dir ();
286     if (G_LIKELY (dirname))
287     {
288         filepath = g_build_filename (dirname, "accounts/services",
289                                               filename, NULL);
290         if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
291             goto found;
292         g_free (filepath);
293     }
294
295     dirs = g_get_system_data_dirs ();
296     for (dirname = *dirs; dirname != NULL; dirs++, dirname = *dirs)
297     {
298         filepath = g_build_filename (dirname, "accounts/services",
299                                               filename, NULL);
300         if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
301             goto found;
302         g_free (filepath);
303     }
304
305     filepath = NULL;
306 found:
307     g_free (filename);
308     return filepath;
309 }
310
311 AgService *
312 _ag_service_new (void)
313 {
314     AgService *service;
315
316     service = g_slice_new0 (AgService);
317     service->ref_count = 1;
318
319     return service;
320 }
321
322 static gboolean
323 _ag_service_load_from_file (AgService *service)
324 {
325     xmlTextReaderPtr reader;
326     gchar *filepath;
327     gboolean ret;
328     GError *error = NULL;
329     gsize len;
330
331     g_return_val_if_fail (service->name != NULL, FALSE);
332
333     g_debug ("Loading service %s", service->name);
334     filepath = find_service_file (service->name);
335     if (G_UNLIKELY (!filepath)) return FALSE;
336
337     g_file_get_contents (filepath, &service->file_data,
338                          &len, &error);
339     if (G_UNLIKELY (error))
340     {
341         g_warning ("Error reading %s: %s", filepath, error->message);
342         g_error_free (error);
343         g_free (filepath);
344         return FALSE;
345     }
346
347     g_free (filepath);
348
349     /* TODO: cache the xmlReader */
350     reader = xmlReaderForMemory (service->file_data, len,
351                                  NULL, NULL, 0);
352     if (G_UNLIKELY (reader == NULL))
353         return FALSE;
354
355     ret = read_service_file (reader, service);
356
357     xmlFreeTextReader (reader);
358     return ret;
359 }
360
361 AgService *
362 _ag_service_new_from_file (const gchar *service_name)
363 {
364     AgService *service;
365
366     service = _ag_service_new ();
367     service->name = g_strdup (service_name);
368     if (!_ag_service_load_from_file (service))
369     {
370         ag_service_unref (service);
371         service = NULL;
372     }
373
374     return service;
375 }
376
377 GHashTable *
378 _ag_service_load_default_settings (AgService *service)
379 {
380     g_return_val_if_fail (service != NULL, NULL);
381
382     if (!service->default_settings)
383     {
384         /* This can happen if the service was created by the AccountManager by
385          * loading the record from the DB.
386          * Now we must reload the service from its XML file.
387          */
388         if (!_ag_service_load_from_file (service))
389         {
390             g_warning ("Loading service %s file failed", service->name);
391             return NULL;
392         }
393     }
394
395     return service->default_settings;
396 }
397
398 const GValue *
399 _ag_service_get_default_setting (AgService *service, const gchar *key)
400 {
401     GHashTable *settings;
402
403     g_return_val_if_fail (key != NULL, NULL);
404
405     settings = _ag_service_load_default_settings (service);
406     if (G_UNLIKELY (!settings))
407         return NULL;
408
409     return g_hash_table_lookup (settings, key);
410 }
411
412 /**
413  * ag_service_get_name:
414  * @service: the #AgService.
415  *
416  * Returns: the name of @service.
417  */
418 const gchar *
419 ag_service_get_name (AgService *service)
420 {
421     g_return_val_if_fail (service != NULL, NULL);
422     return service->name;
423 }
424
425 /**
426  * ag_service_get_display_name:
427  * @service: the #AgService.
428  *
429  * Returns: the display name of @service.
430  */
431 const gchar *
432 ag_service_get_display_name (AgService *service)
433 {
434     g_return_val_if_fail (service != NULL, NULL);
435     return service->display_name;
436 }
437
438 /**
439  * ag_service_get_service_type:
440  * @service: the #AgService.
441  *
442  * Returns: the type of @service.
443  */
444 const gchar *
445 ag_service_get_service_type (AgService *service)
446 {
447     g_return_val_if_fail (service != NULL, NULL);
448     return service->type;
449 }
450
451 /**
452  * ag_service_get_provider:
453  * @service: the #AgService.
454  *
455  * Returns: the name of the provider of @service.
456  */
457 const gchar *
458 ag_service_get_provider (AgService *service)
459 {
460     g_return_val_if_fail (service != NULL, NULL);
461     return service->provider;
462 }
463
464 /**
465  * ag_service_get_icon_name:
466  * @service: the #AgService.
467  *
468  * Returns: the name of the icon of @service.
469  */
470 const gchar *
471 ag_service_get_icon_name (AgService *service)
472 {
473     g_return_val_if_fail (service != NULL, NULL);
474
475     if (!service->file_data)
476         _ag_service_load_from_file (service);
477
478     return service->icon_name;
479 }
480
481
482
483 /**
484  * ag_service_get_file_contents:
485  * @service: the #AgService.
486  * @contents: location to receive the pointer to the file contents.
487  * @data_offset: pointer to receive the offset of the type data.
488  *
489  * Gets the contents of the XML service file.  The buffer returned in @contents
490  * should not be modified or freed, and is guaranteed to be valid as long as
491  * @service is referenced. If @data_offset is not %NULL, it is set to the
492  * offset where the &lt;type_data&gt; element can be found.
493  * If some error occurs, @contents is set to %NULL.
494  */
495 void
496 ag_service_get_file_contents (AgService *service,
497                               const gchar **contents,
498                               gsize *data_offset)
499 {
500     g_return_if_fail (service != NULL);
501     g_return_if_fail (contents != NULL);
502
503     if (service->file_data == NULL)
504     {
505         /* This can happen if the service was created by the AccountManager by
506          * loading the record from the DB.
507          * Now we must reload the service from its XML file.
508          */
509         if (!_ag_service_load_from_file (service))
510             g_warning ("Loading service %s file failed", service->name);
511     }
512
513     *contents = service->file_data;
514
515     if (data_offset)
516         *data_offset = service->type_data_offset;
517 }
518
519 /**
520  * ag_service_ref:
521  * @service: the #AgService.
522  *
523  * Adds a reference to @service.
524  *
525  * Returns: @service.
526  */
527 AgService *
528 ag_service_ref (AgService *service)
529 {
530     g_return_val_if_fail (service != NULL, NULL);
531     g_return_val_if_fail (service->ref_count > 0, NULL);
532
533     g_debug ("Referencing service %s (%d)",
534              service->name, service->ref_count);
535     service->ref_count++;
536     return service;
537 }
538
539 /**
540  * ag_service_unref:
541  * @service: the #AgService.
542  *
543  * Used to unreference the #AgService structure.
544  */
545 void
546 ag_service_unref (AgService *service)
547 {
548     g_return_if_fail (service != NULL);
549     g_return_if_fail (service->ref_count > 0);
550
551     g_debug ("Unreferencing service %s (%d)",
552              service->name, service->ref_count);
553     service->ref_count--;
554     if (service->ref_count == 0)
555     {
556         g_free (service->name);
557         g_free (service->display_name);
558         g_free (service->type);
559         g_free (service->provider);
560         g_free (service->file_data);
561         if (service->default_settings)
562             g_hash_table_unref (service->default_settings);
563         g_slice_free (AgService, service);
564     }
565 }
566
567 /**
568  * ag_service_list_free:
569  * @list: a #GList of services returned by some function of this library.
570  *
571  * Frees the list @list.
572  */
573 void
574 ag_service_list_free (GList *list)
575 {
576     g_list_foreach (list, (GFunc)ag_service_unref, NULL);
577     g_list_free (list);
578 }
579