Merge the keyfile settings backend.
[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_WITH_CODE (GKeyfileSettingsBackend,
41                          g_keyfile_settings_backend,
42                          G_TYPE_SETTINGS_BACKEND,
43                          g_io_extension_point_implement (G_SETTINGS_BACKEND_EXTENSION_POINT_NAME,
44                                                          g_define_type_id, "keyfile", 0))
45
46 struct _GKeyfileSettingsBackendPrivate
47 {
48   GHashTable   *table;
49   GKeyFile     *keyfile;
50   gboolean      writable;
51   gchar        *file_path;
52   gchar        *checksum;
53   GFileMonitor *monitor;
54 };
55
56 static GVariant *
57 g_keyfile_settings_backend_read (GSettingsBackend   *backend,
58                                  const gchar        *key,
59                                  const GVariantType *expected_type)
60 {
61   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
62   GVariant *value;
63
64   value = g_hash_table_lookup (kf_backend->priv->table, key);
65
66   if (value != NULL)
67     g_variant_ref (value);
68
69   return value;
70 }
71
72 static gboolean
73 g_keyfile_settings_backend_write_one (const gchar             *key,
74                                       GVariant                *value,
75                                       GKeyfileSettingsBackend *kf_backend)
76 {
77   const gchar *slash;
78   const gchar *base_key;
79   gchar       *path;
80
81   g_hash_table_replace (kf_backend->priv->table,
82                         g_strdup (key), g_variant_ref (value));
83
84   slash = strrchr (key, '/');
85   g_assert (slash != NULL);
86   base_key = (slash + 1);
87   path = g_strndup (key, slash - key + 1);
88
89   g_key_file_set_string (kf_backend->priv->keyfile,
90                          path, base_key, g_variant_print (value, TRUE));
91
92   g_free (path);
93
94   return FALSE;
95 }
96
97 static void
98 g_keyfile_settings_backend_keyfile_write (GKeyfileSettingsBackend *kf_backend)
99 {
100   gchar *dirname;
101   gchar *contents;
102   gsize  length;
103   GFile *file;
104
105   dirname = g_path_get_dirname (kf_backend->priv->file_path);
106   if (!g_file_test (dirname, G_FILE_TEST_IS_DIR))
107     g_mkdir_with_parents (dirname, 0700);
108   g_free (dirname);
109
110   contents = g_key_file_to_data (kf_backend->priv->keyfile, &length, NULL);
111
112   file = g_file_new_for_path (kf_backend->priv->file_path);
113   g_file_replace_contents (file, contents, length,
114                            NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,
115                            NULL, NULL, NULL);
116   g_object_unref (file);
117
118   g_free (kf_backend->priv->checksum);
119   kf_backend->priv->checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA256, contents, length);
120
121   g_free (contents);
122 }
123
124 static gboolean
125 g_keyfile_settings_backend_write (GSettingsBackend *backend,
126                                   const gchar      *key,
127                                   GVariant         *value,
128                                   gpointer          origin_tag)
129 {
130   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
131
132   g_keyfile_settings_backend_write_one (key, value, kf_backend);
133   g_keyfile_settings_backend_keyfile_write (kf_backend);
134
135   g_settings_backend_changed (backend, key, origin_tag);
136
137   return TRUE;
138 }
139
140 static gboolean
141 g_keyfile_settings_backend_write_keys (GSettingsBackend *backend,
142                                        GTree            *tree,
143                                        gpointer          origin_tag)
144 {
145   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
146
147   g_tree_foreach (tree, (GTraverseFunc) g_keyfile_settings_backend_write_one, backend);
148   g_keyfile_settings_backend_keyfile_write (kf_backend);
149
150   g_settings_backend_changed_tree (backend, tree, origin_tag);
151
152   return TRUE;
153 }
154
155 static void
156 g_keyfile_settings_backend_reset_path (GSettingsBackend *backend,
157                                        const gchar      *path,
158                                        gpointer         origin_tag)
159 {
160   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
161   GPtrArray  *reset_array;
162   GList      *hash_keys;
163   GList      *l;
164   gboolean    changed;
165   gchar     **groups = NULL;
166   gsize       groups_nb = 0;
167   int         i;
168
169   reset_array = g_ptr_array_new_with_free_func (g_free);
170
171   hash_keys = g_hash_table_get_keys (kf_backend->priv->table);
172   for (l = hash_keys; l != NULL; l = l->next)
173     {
174       if (g_str_has_prefix (l->data, path))
175         {
176           g_hash_table_remove (kf_backend->priv->table, l->data);
177           g_ptr_array_add (reset_array, g_strdup (l->data));
178         }
179     }
180   g_list_free (hash_keys);
181
182   changed = FALSE;
183   groups = g_key_file_get_groups (kf_backend->priv->keyfile, &groups_nb);
184   for (i = 0; i < groups_nb; i++)
185     {
186       if (g_str_has_prefix (groups[i], path))
187         changed = g_key_file_remove_group (kf_backend->priv->keyfile, groups[i], NULL) || changed;
188     }
189   g_strfreev (groups);
190
191   if (changed)
192     g_keyfile_settings_backend_keyfile_write (kf_backend);
193
194   if (reset_array->len > 0)
195     {
196       /* the array has to be NULL-terminated */
197       g_ptr_array_add (reset_array, NULL);
198       g_settings_backend_keys_changed (G_SETTINGS_BACKEND (kf_backend),
199                                        "",
200                                        (const gchar **) reset_array->pdata,
201                                        origin_tag);
202     }
203
204   g_ptr_array_free (reset_array, TRUE);
205 }
206
207 static void
208 g_keyfile_settings_backend_reset (GSettingsBackend *backend,
209                                   const gchar      *key,
210                                   gpointer          origin_tag)
211 {
212   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
213   gboolean     had_key;
214   const gchar *slash;
215   const gchar *base_key;
216   gchar       *path;
217
218   had_key = g_hash_table_lookup_extended (kf_backend->priv->table, key, NULL, NULL);
219   if (had_key)
220     g_hash_table_remove (kf_backend->priv->table, key);
221
222   slash = strrchr (key, '/');
223   g_assert (slash != NULL);
224   base_key = (slash + 1);
225   path = g_strndup (key, slash - key + 1);
226
227   if (g_key_file_remove_key (kf_backend->priv->keyfile, path, base_key, NULL))
228     g_keyfile_settings_backend_keyfile_write (kf_backend);
229
230   g_free (path);
231
232   if (had_key)
233     g_settings_backend_changed (G_SETTINGS_BACKEND (kf_backend), key, origin_tag);
234 }
235
236 static gboolean
237 g_keyfile_settings_backend_get_writable (GSettingsBackend *backend,
238                                          const gchar      *name)
239 {
240   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (backend);
241
242   return kf_backend->priv->writable;
243 }
244
245 static gboolean
246 g_keyfile_settings_backend_supports_context (const gchar *context)
247 {
248   return TRUE;
249 }
250
251 static void
252 g_keyfile_settings_backend_keyfile_reload (GKeyfileSettingsBackend *kf_backend)
253 {
254   gchar       *contents = NULL;
255   gsize        length = 0;
256   gchar       *new_checksum;
257   GHashTable  *loaded_keys;
258   GPtrArray   *changed_array;
259   gchar      **groups = NULL;
260   gsize        groups_nb = 0;
261   int          i;
262   GList       *keys_l = NULL;
263   GList       *l;
264
265   if (!g_file_get_contents (kf_backend->priv->file_path,
266                             &contents, &length, NULL))
267     {
268       contents = g_strdup ("");
269       length = 0;
270     }
271
272   new_checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA256, contents, length);
273
274   if (g_strcmp0 (kf_backend->priv->checksum, new_checksum) == 0)
275     {
276       g_free (new_checksum);
277       return;
278     }
279
280   if (kf_backend->priv->checksum != NULL)
281     g_free (kf_backend->priv->checksum);
282   kf_backend->priv->checksum = new_checksum;
283
284   if (kf_backend->priv->keyfile != NULL)
285     g_key_file_free (kf_backend->priv->keyfile);
286
287   kf_backend->priv->keyfile = g_key_file_new ();
288
289   /* we just silently ignore errors: there's not much we can do about them */
290   if (length > 0)
291     g_key_file_load_from_data (kf_backend->priv->keyfile, contents, length,
292                                G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
293
294   loaded_keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
295   changed_array = g_ptr_array_new_with_free_func (g_free);
296
297   /* Load keys from the keyfile */
298   groups = g_key_file_get_groups (kf_backend->priv->keyfile, &groups_nb);
299   for (i = 0; i < groups_nb; i++)
300     {
301       gchar **keys = NULL;
302       gsize   keys_nb = 0;
303       int     j;
304
305       keys = g_key_file_get_keys (kf_backend->priv->keyfile, groups[i], &keys_nb, NULL);
306       if (keys == NULL)
307         continue;
308
309       for (j = 0; j < keys_nb; j++)
310         {
311           gchar    *value;
312           gchar    *full_key;
313           GVariant *old_variant;
314           GVariant *variant;
315
316           value = g_key_file_get_string (kf_backend->priv->keyfile, groups[i], keys[j], NULL);
317           if (value == NULL)
318             continue;
319
320           variant = g_variant_new_parsed (value);
321           g_free (value);
322
323           if (variant == NULL)
324             continue;
325
326           full_key = g_strjoin ("", groups[i], keys[j], NULL),
327           g_hash_table_insert (loaded_keys, full_key, GINT_TO_POINTER(TRUE));
328
329           old_variant = g_hash_table_lookup (kf_backend->priv->table, full_key);
330
331           if (old_variant == NULL || !g_variant_equal (old_variant, variant))
332             {
333               g_ptr_array_add (changed_array, g_strdup (full_key));
334               g_hash_table_replace (kf_backend->priv->table,
335                                     g_strdup (full_key),
336                                     g_variant_ref_sink (variant));
337             }
338           else
339             g_variant_unref (variant);
340         }
341
342       g_strfreev (keys);
343     }
344   g_strfreev (groups);
345
346   /* Remove keys that were in the hashtable but not in the keyfile */
347   keys_l = g_hash_table_get_keys (kf_backend->priv->table);
348   for (l = keys_l; l != NULL; l = l->next)
349     {
350       gchar *key = l->data;
351
352       if (g_hash_table_lookup_extended (loaded_keys, key, NULL, NULL))
353         continue;
354
355       g_ptr_array_add (changed_array, g_strdup (key));
356       g_hash_table_remove (kf_backend->priv->table, key);
357     }
358   g_list_free (keys_l);
359
360   if (changed_array->len > 0)
361     {
362       /* the array has to be NULL-terminated */
363       g_ptr_array_add (changed_array, NULL);
364       g_settings_backend_keys_changed (G_SETTINGS_BACKEND (kf_backend),
365                                        "",
366                                        (const gchar **) changed_array->pdata,
367                                        NULL);
368     }
369
370   g_hash_table_unref (loaded_keys);
371   g_ptr_array_free (changed_array, TRUE);
372 }
373
374 static gboolean
375 g_keyfile_settings_backend_keyfile_writable (GFile *file)
376 {
377   GFileInfo *fileinfo;
378   GError    *error;
379   gboolean   writable = FALSE;
380
381   error = NULL;
382   fileinfo = g_file_query_info (file, "access::*",
383                                 G_FILE_QUERY_INFO_NONE, NULL, &error);
384
385   if (fileinfo == NULL)
386     {
387       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
388         {
389           GFile *parent;
390
391           parent = g_file_get_parent (file);
392           if (parent)
393             {
394               writable = g_keyfile_settings_backend_keyfile_writable (parent);
395               g_object_unref (parent);
396             }
397         }
398
399       g_error_free (error);
400
401       return writable;
402     }
403
404   /* We don't want to mark the backend as writable if the file is not readable,
405    * since it means we won't be able to load the content of the file, and we'll
406    * lose data. */
407   writable =
408         g_file_info_get_attribute_boolean (fileinfo, G_FILE_ATTRIBUTE_ACCESS_CAN_READ) &&
409         g_file_info_get_attribute_boolean (fileinfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
410   g_object_unref (fileinfo);
411
412   return writable;
413 }
414
415 static void
416 g_keyfile_settings_backend_keyfile_changed (GFileMonitor      *monitor,
417                                             GFile             *file,
418                                             GFile             *other_file,
419                                             GFileMonitorEvent  event_type,
420                                             gpointer           user_data)
421 {
422   GKeyfileSettingsBackend *kf_backend;
423
424   if (event_type != G_FILE_MONITOR_EVENT_CHANGED &&
425       event_type != G_FILE_MONITOR_EVENT_CREATED &&
426       event_type != G_FILE_MONITOR_EVENT_DELETED &&
427       event_type != G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED)
428     return;
429
430   kf_backend = G_KEYFILE_SETTINGS_BACKEND (user_data);
431
432   if (event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED)
433     {
434       gboolean writable;
435
436       writable = g_keyfile_settings_backend_keyfile_writable (file);
437
438       if (kf_backend->priv->writable == writable)
439         return;
440
441       kf_backend->priv->writable = writable;
442       if (!writable)
443         return;
444       /* else: reload the file since it was possibly not readable before */
445     }
446
447   g_keyfile_settings_backend_keyfile_reload (kf_backend);
448 }
449
450 static void
451 g_keyfile_settings_backend_finalize (GObject *object)
452 {
453   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (object);
454
455   g_hash_table_unref (kf_backend->priv->table);
456   kf_backend->priv->table = NULL;
457
458   g_key_file_free (kf_backend->priv->keyfile);
459   kf_backend->priv->keyfile = NULL;
460
461   g_free (kf_backend->priv->file_path);
462   kf_backend->priv->file_path = NULL;
463
464   g_free (kf_backend->priv->checksum);
465   kf_backend->priv->checksum = NULL;
466
467   g_file_monitor_cancel (kf_backend->priv->monitor);
468   g_object_unref (kf_backend->priv->monitor);
469   kf_backend->priv->monitor = NULL;
470
471   G_OBJECT_CLASS (g_keyfile_settings_backend_parent_class)
472     ->finalize (object);
473 }
474
475 static void
476 g_keyfile_settings_backend_constructed (GObject *object)
477 {
478   GKeyfileSettingsBackend *kf_backend = G_KEYFILE_SETTINGS_BACKEND (object);
479   const gchar             *envvar_path;
480   gchar                   *path;
481   gchar                   *context;
482   GFile                   *file;
483
484   envvar_path = g_getenv ("GSETTINGS_KEYFILE_BACKEND_STORE");
485   if (envvar_path != NULL && envvar_path[0] != '\0')
486     path = g_strdup (envvar_path);
487   else
488     path = g_build_filename (g_get_user_config_dir (),
489                              "gsettings", "store", NULL);
490
491   context = NULL;
492   g_object_get (kf_backend, "context", &context, NULL);
493   if (context && context[0] != '\0')
494     {
495       kf_backend->priv->file_path = g_strdup_printf ("%s.%s", path, context);
496       g_free (context);
497       g_free (path);
498     }
499   else
500     kf_backend->priv->file_path = path;
501
502   file = g_file_new_for_path (kf_backend->priv->file_path);
503
504   kf_backend->priv->writable = g_keyfile_settings_backend_keyfile_writable (file);
505
506   kf_backend->priv->monitor = g_file_monitor_file (file, G_FILE_MONITOR_SEND_MOVED, NULL, NULL);
507   g_signal_connect (kf_backend->priv->monitor, "changed",
508                     (GCallback)g_keyfile_settings_backend_keyfile_changed, kf_backend);
509
510   g_object_unref (file);
511
512   g_keyfile_settings_backend_keyfile_reload (kf_backend);
513 }
514
515 static void
516 g_keyfile_settings_backend_init (GKeyfileSettingsBackend *kf_backend)
517 {
518   kf_backend->priv = G_TYPE_INSTANCE_GET_PRIVATE (kf_backend,
519                                                   G_TYPE_KEYFILE_SETTINGS_BACKEND,
520                                                   GKeyfileSettingsBackendPrivate);
521   kf_backend->priv->table =
522     g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
523                            (GDestroyNotify) g_variant_unref);
524
525   kf_backend->priv->keyfile = NULL;
526   kf_backend->priv->writable = FALSE;
527   kf_backend->priv->file_path = NULL;
528   kf_backend->priv->checksum = NULL;
529   kf_backend->priv->monitor = NULL;
530 }
531
532 static void
533 g_keyfile_settings_backend_class_init (GKeyfileSettingsBackendClass *class)
534 {
535   GSettingsBackendClass *backend_class = G_SETTINGS_BACKEND_CLASS (class);
536   GObjectClass *object_class = G_OBJECT_CLASS (class);
537
538   object_class->constructed = g_keyfile_settings_backend_constructed;
539   object_class->finalize = g_keyfile_settings_backend_finalize;
540
541   backend_class->read = g_keyfile_settings_backend_read;
542   backend_class->write = g_keyfile_settings_backend_write;
543   backend_class->write_keys = g_keyfile_settings_backend_write_keys;
544   backend_class->reset = g_keyfile_settings_backend_reset;
545   backend_class->reset_path = g_keyfile_settings_backend_reset_path;
546   backend_class->get_writable = g_keyfile_settings_backend_get_writable;
547   /* No need to implement subscribed/unsubscribe: the only point would be to
548    * stop monitoring the file when there's no GSettings anymore, which is no
549    * big win. */
550   backend_class->supports_context = g_keyfile_settings_backend_supports_context;
551
552   g_type_class_add_private (class, sizeof (GKeyfileSettingsBackendPrivate));
553 }