add backend setup APIs
[platform/upstream/glib.git] / gio / gkeyfilesettingsbackend.c
1 /*
2  * Copyright © 2010 Codethink Limited
3  * Copyright © 2010 Novell, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the licence, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Authors: Vincent Untz <vuntz@gnome.org>
21  *          Ryan Lortie <desrt@desrt.ca>
22  */
23
24 #include "config.h"
25
26 #include "gkeyfilesettingsbackend.h"
27
28 #include <stdio.h>
29 #include <string.h>
30 #include <errno.h>
31
32 #include "gioerror.h"
33 #include "giomodule.h"
34 #include "gfile.h"
35 #include "gfileinfo.h"
36 #include "gfilemonitor.h"
37
38 #include "gioalias.h"
39
40 G_DEFINE_TYPE (GKeyfileSettingsBackend,
41                g_keyfile_settings_backend,
42                G_TYPE_SETTINGS_BACKEND)
43
44 struct _GKeyfileSettingsBackendPrivate
45 {
46   GHashTable   *table;
47   GKeyFile     *keyfile;
48   gboolean      writable;
49   gchar        *file_path;
50   gchar        *checksum;
51   GFileMonitor *monitor;
52 };
53
54 static GVariant *
55 g_keyfile_settings_backend_read (GSettingsBackend   *backend,
56                                  const gchar        *key,
57                                  const GVariantType *expected_type)
58 {
59   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
60   GVariant *value;
61
62   value = g_hash_table_lookup (kf_backend->priv->table, key);
63
64   if (value != NULL)
65     g_variant_ref (value);
66
67   return value;
68 }
69
70 static gboolean
71 g_keyfile_settings_backend_write_one (const gchar             *key,
72                                       GVariant                *value,
73                                       GKeyfileSettingsBackend *kf_backend)
74 {
75   const gchar *slash;
76   const gchar *base_key;
77   gchar       *path;
78
79   g_hash_table_replace (kf_backend->priv->table,
80                         g_strdup (key), g_variant_ref (value));
81
82   slash = strrchr (key, '/');
83   g_assert (slash != NULL);
84   base_key = (slash + 1);
85   path = g_strndup (key, slash - key + 1);
86
87   g_key_file_set_string (kf_backend->priv->keyfile,
88                          path, base_key, g_variant_print (value, TRUE));
89
90   g_free (path);
91
92   return FALSE;
93 }
94
95 static void
96 g_keyfile_settings_backend_keyfile_write (GKeyfileSettingsBackend *kf_backend)
97 {
98   gchar *dirname;
99   gchar *contents;
100   gsize  length;
101   GFile *file;
102
103   dirname = g_path_get_dirname (kf_backend->priv->file_path);
104   if (!g_file_test (dirname, G_FILE_TEST_IS_DIR))
105     g_mkdir_with_parents (dirname, 0700);
106   g_free (dirname);
107
108   contents = g_key_file_to_data (kf_backend->priv->keyfile, &length, NULL);
109
110   file = g_file_new_for_path (kf_backend->priv->file_path);
111   g_file_replace_contents (file, contents, length,
112                            NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,
113                            NULL, NULL, NULL);
114   g_object_unref (file);
115
116   g_free (kf_backend->priv->checksum);
117   kf_backend->priv->checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA256, contents, length);
118
119   g_free (contents);
120 }
121
122 static gboolean
123 g_keyfile_settings_backend_write (GSettingsBackend *backend,
124                                   const gchar      *key,
125                                   GVariant         *value,
126                                   gpointer          origin_tag)
127 {
128   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
129
130   g_keyfile_settings_backend_write_one (key, value, kf_backend);
131   g_keyfile_settings_backend_keyfile_write (kf_backend);
132
133   g_settings_backend_changed (backend, key, origin_tag);
134
135   return TRUE;
136 }
137
138 static gboolean
139 g_keyfile_settings_backend_write_keys (GSettingsBackend *backend,
140                                        GTree            *tree,
141                                        gpointer          origin_tag)
142 {
143   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
144
145   g_tree_foreach (tree, (GTraverseFunc) g_keyfile_settings_backend_write_one, backend);
146   g_keyfile_settings_backend_keyfile_write (kf_backend);
147
148   g_settings_backend_changed_tree (backend, tree, origin_tag);
149
150   return TRUE;
151 }
152
153 static void
154 g_keyfile_settings_backend_reset_path (GSettingsBackend *backend,
155                                        const gchar      *path,
156                                        gpointer         origin_tag)
157 {
158   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
159   GPtrArray  *reset_array;
160   GList      *hash_keys;
161   GList      *l;
162   gboolean    changed;
163   gchar     **groups = NULL;
164   gsize       groups_nb = 0;
165   int         i;
166
167   reset_array = g_ptr_array_new_with_free_func (g_free);
168
169   hash_keys = g_hash_table_get_keys (kf_backend->priv->table);
170   for (l = hash_keys; l != NULL; l = l->next)
171     {
172       if (g_str_has_prefix (l->data, path))
173         {
174           g_hash_table_remove (kf_backend->priv->table, l->data);
175           g_ptr_array_add (reset_array, g_strdup (l->data));
176         }
177     }
178   g_list_free (hash_keys);
179
180   changed = FALSE;
181   groups = g_key_file_get_groups (kf_backend->priv->keyfile, &groups_nb);
182   for (i = 0; i < groups_nb; i++)
183     {
184       if (g_str_has_prefix (groups[i], path))
185         changed = g_key_file_remove_group (kf_backend->priv->keyfile, groups[i], NULL) || changed;
186     }
187   g_strfreev (groups);
188
189   if (changed)
190     g_keyfile_settings_backend_keyfile_write (kf_backend);
191
192   if (reset_array->len > 0)
193     {
194       /* the array has to be NULL-terminated */
195       g_ptr_array_add (reset_array, NULL);
196       g_settings_backend_keys_changed (G_SETTINGS_BACKEND (kf_backend),
197                                        "",
198                                        (const gchar **) reset_array->pdata,
199                                        origin_tag);
200     }
201
202   g_ptr_array_free (reset_array, TRUE);
203 }
204
205 static void
206 g_keyfile_settings_backend_reset (GSettingsBackend *backend,
207                                   const gchar      *key,
208                                   gpointer          origin_tag)
209 {
210   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
211   gboolean     had_key;
212   const gchar *slash;
213   const gchar *base_key;
214   gchar       *path;
215
216   had_key = g_hash_table_lookup_extended (kf_backend->priv->table, key, NULL, NULL);
217   if (had_key)
218     g_hash_table_remove (kf_backend->priv->table, key);
219
220   slash = strrchr (key, '/');
221   g_assert (slash != NULL);
222   base_key = (slash + 1);
223   path = g_strndup (key, slash - key + 1);
224
225   if (g_key_file_remove_key (kf_backend->priv->keyfile, path, base_key, NULL))
226     g_keyfile_settings_backend_keyfile_write (kf_backend);
227
228   g_free (path);
229
230   if (had_key)
231     g_settings_backend_changed (G_SETTINGS_BACKEND (kf_backend), key, origin_tag);
232 }
233
234 static gboolean
235 g_keyfile_settings_backend_get_writable (GSettingsBackend *backend,
236                                          const gchar      *name)
237 {
238   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
239
240   return kf_backend->priv->writable;
241 }
242
243 static void
244 g_keyfile_settings_backend_keyfile_reload (GKeyfileSettingsBackend *kf_backend)
245 {
246   gchar       *contents = NULL;
247   gsize        length = 0;
248   gchar       *new_checksum;
249   GHashTable  *loaded_keys;
250   GPtrArray   *changed_array;
251   gchar      **groups = NULL;
252   gsize        groups_nb = 0;
253   int          i;
254   GList       *keys_l = NULL;
255   GList       *l;
256
257   if (!g_file_get_contents (kf_backend->priv->file_path,
258                             &contents, &length, NULL))
259     {
260       contents = g_strdup ("");
261       length = 0;
262     }
263
264   new_checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA256, contents, length);
265
266   if (g_strcmp0 (kf_backend->priv->checksum, new_checksum) == 0)
267     {
268       g_free (new_checksum);
269       return;
270     }
271
272   if (kf_backend->priv->checksum != NULL)
273     g_free (kf_backend->priv->checksum);
274   kf_backend->priv->checksum = new_checksum;
275
276   if (kf_backend->priv->keyfile != NULL)
277     g_key_file_free (kf_backend->priv->keyfile);
278
279   kf_backend->priv->keyfile = g_key_file_new ();
280
281   /* we just silently ignore errors: there's not much we can do about them */
282   if (length > 0)
283     g_key_file_load_from_data (kf_backend->priv->keyfile, contents, length,
284                                G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
285
286   loaded_keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
287   changed_array = g_ptr_array_new_with_free_func (g_free);
288
289   /* Load keys from the keyfile */
290   groups = g_key_file_get_groups (kf_backend->priv->keyfile, &groups_nb);
291   for (i = 0; i < groups_nb; i++)
292     {
293       gchar **keys = NULL;
294       gsize   keys_nb = 0;
295       int     j;
296
297       keys = g_key_file_get_keys (kf_backend->priv->keyfile, groups[i], &keys_nb, NULL);
298       if (keys == NULL)
299         continue;
300
301       for (j = 0; j < keys_nb; j++)
302         {
303           gchar    *value;
304           gchar    *full_key;
305           GVariant *old_variant;
306           GVariant *variant;
307
308           value = g_key_file_get_string (kf_backend->priv->keyfile, groups[i], keys[j], NULL);
309           if (value == NULL)
310             continue;
311
312           variant = g_variant_new_parsed (value);
313           g_free (value);
314
315           if (variant == NULL)
316             continue;
317
318           full_key = g_strjoin ("", groups[i], keys[j], NULL),
319           g_hash_table_insert (loaded_keys, full_key, GINT_TO_POINTER(TRUE));
320
321           old_variant = g_hash_table_lookup (kf_backend->priv->table, full_key);
322
323           if (old_variant == NULL || !g_variant_equal (old_variant, variant))
324             {
325               g_ptr_array_add (changed_array, g_strdup (full_key));
326               g_hash_table_replace (kf_backend->priv->table,
327                                     g_strdup (full_key),
328                                     g_variant_ref_sink (variant));
329             }
330           else
331             g_variant_unref (variant);
332         }
333
334       g_strfreev (keys);
335     }
336   g_strfreev (groups);
337
338   /* Remove keys that were in the hashtable but not in the keyfile */
339   keys_l = g_hash_table_get_keys (kf_backend->priv->table);
340   for (l = keys_l; l != NULL; l = l->next)
341     {
342       gchar *key = l->data;
343
344       if (g_hash_table_lookup_extended (loaded_keys, key, NULL, NULL))
345         continue;
346
347       g_ptr_array_add (changed_array, g_strdup (key));
348       g_hash_table_remove (kf_backend->priv->table, key);
349     }
350   g_list_free (keys_l);
351
352   if (changed_array->len > 0)
353     {
354       /* the array has to be NULL-terminated */
355       g_ptr_array_add (changed_array, NULL);
356       g_settings_backend_keys_changed (G_SETTINGS_BACKEND (kf_backend),
357                                        "",
358                                        (const gchar **) changed_array->pdata,
359                                        NULL);
360     }
361
362   g_hash_table_unref (loaded_keys);
363   g_ptr_array_free (changed_array, TRUE);
364 }
365
366 static gboolean
367 g_keyfile_settings_backend_keyfile_writable (GFile *file)
368 {
369   GFileInfo *fileinfo;
370   GError    *error;
371   gboolean   writable = FALSE;
372
373   error = NULL;
374   fileinfo = g_file_query_info (file, "access::*",
375                                 G_FILE_QUERY_INFO_NONE, NULL, &error);
376
377   if (fileinfo == NULL)
378     {
379       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
380         {
381           GFile *parent;
382
383           parent = g_file_get_parent (file);
384           if (parent)
385             {
386               writable = g_keyfile_settings_backend_keyfile_writable (parent);
387               g_object_unref (parent);
388             }
389         }
390
391       g_error_free (error);
392
393       return writable;
394     }
395
396   /* We don't want to mark the backend as writable if the file is not readable,
397    * since it means we won't be able to load the content of the file, and we'll
398    * lose data. */
399   writable =
400         g_file_info_get_attribute_boolean (fileinfo, G_FILE_ATTRIBUTE_ACCESS_CAN_READ) &&
401         g_file_info_get_attribute_boolean (fileinfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
402   g_object_unref (fileinfo);
403
404   return writable;
405 }
406
407 static void
408 g_keyfile_settings_backend_keyfile_changed (GFileMonitor      *monitor,
409                                             GFile             *file,
410                                             GFile             *other_file,
411                                             GFileMonitorEvent  event_type,
412                                             gpointer           user_data)
413 {
414   GKeyfileSettingsBackend *kf_backend;
415
416   if (event_type != G_FILE_MONITOR_EVENT_CHANGED &&
417       event_type != G_FILE_MONITOR_EVENT_CREATED &&
418       event_type != G_FILE_MONITOR_EVENT_DELETED &&
419       event_type != G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED)
420     return;
421
422   kf_backend = G_KEYFILE_SETTINGS_BACKEND (user_data);
423
424   if (event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED)
425     {
426       gboolean writable;
427
428       writable = g_keyfile_settings_backend_keyfile_writable (file);
429
430       if (kf_backend->priv->writable == writable)
431         return;
432
433       kf_backend->priv->writable = writable;
434       if (!writable)
435         return;
436       /* else: reload the file since it was possibly not readable before */
437     }
438
439   g_keyfile_settings_backend_keyfile_reload (kf_backend);
440 }
441
442 static void
443 g_keyfile_settings_backend_finalize (GObject *object)
444 {
445   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (object);
446
447   g_hash_table_unref (kf_backend->priv->table);
448   kf_backend->priv->table = NULL;
449
450   g_key_file_free (kf_backend->priv->keyfile);
451   kf_backend->priv->keyfile = NULL;
452
453   g_free (kf_backend->priv->file_path);
454   kf_backend->priv->file_path = NULL;
455
456   g_free (kf_backend->priv->checksum);
457   kf_backend->priv->checksum = NULL;
458
459   g_file_monitor_cancel (kf_backend->priv->monitor);
460   g_object_unref (kf_backend->priv->monitor);
461   kf_backend->priv->monitor = NULL;
462
463   G_OBJECT_CLASS (g_keyfile_settings_backend_parent_class)
464     ->finalize (object);
465 }
466
467 static void
468 g_keyfile_settings_backend_init (GKeyfileSettingsBackend *kf_backend)
469 {
470   kf_backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (kf_backend,
471                                                   G_TYPE_KEYFILE_SETTINGS_BACKEND,
472                                                   GKeyfileSettingsBackendPrivate);
473   kf_backend->priv->table =
474     g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
475                            (GDestroyNotify) g_variant_unref);
476
477   kf_backend->priv->keyfile = NULL;
478   kf_backend->priv->writable = FALSE;
479   kf_backend->priv->file_path = NULL;
480   kf_backend->priv->checksum = NULL;
481   kf_backend->priv->monitor = NULL;
482 }
483
484 static void
485 g_keyfile_settings_backend_class_init (GKeyfileSettingsBackendClass *class)
486 {
487   GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
488   GObjectClass *object_class = G_OBJECT_CLASS (class);
489
490   object_class->finalize = g_keyfile_settings_backend_finalize;
491
492   backend_class->read = g_keyfile_settings_backend_read;
493   backend_class->write = g_keyfile_settings_backend_write;
494   backend_class->write_keys = g_keyfile_settings_backend_write_keys;
495   backend_class->reset = g_keyfile_settings_backend_reset;
496   backend_class->reset_path = g_keyfile_settings_backend_reset_path;
497   backend_class->get_writable = g_keyfile_settings_backend_get_writable;
498   /* No need to implement subscribed/unsubscribe: the only point would be to
499    * stop monitoring the file when there's no GSettings anymore, which is no
500    * big win. */
501
502   g_type_class_add_private (class, sizeof (GKeyfileSettingsBackendPrivate));
503 }
504
505 static GKeyfileSettingsBackend *
506 g_keyfile_settings_backend_new (const gchar *filename)
507 {
508   GKeyfileSettingsBackend *kf_backend;
509   GFile *file;
510
511   kf_backend = g_object_new (G_TYPE_KEYFILE_SETTINGS_BACKEND, NULL);
512   kf_backend->priv->file_path = g_strdup (filename);
513
514   file = g_file_new_for_path (kf_backend->priv->file_path);
515
516   kf_backend->priv->writable = g_keyfile_settings_backend_keyfile_writable (file);
517
518   kf_backend->priv->monitor = g_file_monitor_file (file, G_FILE_MONITOR_SEND_MOVED, NULL, NULL);
519   g_signal_connect (kf_backend->priv->monitor, "changed",
520                     (GCallback)g_keyfile_settings_backend_keyfile_changed, kf_backend);
521
522   g_object_unref (file);
523
524   g_keyfile_settings_backend_keyfile_reload (kf_backend);
525
526   return kf_backend;
527 }
528
529 /**
530  * g_settings_backend_setup_keyfile:
531  * @context: a context string (not %NULL or "")
532  * @filename: a filename
533  *
534  * Sets up a keyfile for use with #GSettings.
535  *
536  * If you create a #GSettings with its context property set to @context
537  * then the settings will be stored in the keyfile at @filename.  See
538  * g_settings_new_with_context().
539  *
540  * The keyfile must be setup before any settings objects are created
541  * for the named context.
542  *
543  * It is not possible to specify a keyfile for the default context.
544  *
545  * If the path leading up to @filename does not exist, it will be
546  * recursively created with user-only permissions.  If the keyfile is
547  * not writable, any #GSettings objects created using @context will
548  * return %FALSE for any calls to g_settings_is_writable() and any
549  * attempts to write will fail.
550  */
551 void
552 g_settings_backend_setup_keyfile (const gchar *context,
553                                   const gchar *filename)
554 {
555   GKeyfileSettingsBackend *kf_backend;
556
557   kf_backend = g_keyfile_settings_backend_new (filename);
558   g_settings_backend_setup (context, G_SETTINGS_BACKEND (kf_backend));
559   g_object_unref (kf_backend);
560 }
561
562 #define __G_KEYFILE_SETTINGS_BACKEND_C__
563 #include "gioaliasdef.c"