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