No issues found
1 /*
2 * Copyright (C) 2009, Nokia <ivan.frade@nokia.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library 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 GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include "config.h"
21
22 #include <string.h>
23 #include <stdlib.h>
24
25 #include "tracker-config-file.h"
26
27 #ifdef CONFIG_ENABLE_TRACE
28 #warning Config debugging traces enabled
29 #endif /* CONFIG_ENABLE_TRACE */
30
31 /**
32 * SECTION:tracker-config-file
33 * @short_description: Abstract base class for configuration files
34 * @include: libtracker-common/tracker-common.h
35 *
36 * #TrackerConfigFile is an abstract base class to help creating objects
37 * that proxy a configuration file, mirroring settings to disk and notifying
38 * of changes.
39 **/
40
41 #define TRACKER_CONFIG_FILE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TRACKER_TYPE_CONFIG_FILE, TrackerConfigFilePrivate))
42
43 typedef struct _TrackerConfigFilePrivate TrackerConfigFilePrivate;
44
45 struct _TrackerConfigFilePrivate {
46 gchar *domain;
47 };
48
49 static void config_finalize (GObject *object);
50 static void config_load (TrackerConfigFile *config);
51 static gboolean config_save (TrackerConfigFile *config);
52 static void config_get_property (GObject *object,
53 guint param_id,
54 GValue *value,
55 GParamSpec *pspec);
56 static void config_set_property (GObject *object,
57 guint param_id,
58 const GValue *value,
59 GParamSpec *pspec);
60 static void config_constructed (GObject *object);
61
62 enum {
63 PROP_0,
64 PROP_DOMAIN
65 };
66
67 enum {
68 CHANGED,
69 LAST_SIGNAL
70 };
71
72 static guint signals[LAST_SIGNAL] = { 0, };
73
74 G_DEFINE_TYPE (TrackerConfigFile, tracker_config_file, G_TYPE_OBJECT);
75
76 static void
77 tracker_config_file_class_init (TrackerConfigFileClass *klass)
78 {
79 GObjectClass *object_class = G_OBJECT_CLASS (klass);
80
81 object_class->get_property = config_get_property;
82 object_class->set_property = config_set_property;
83 object_class->finalize = config_finalize;
84 object_class->constructed = config_constructed;
85
86 /**
87 * TrackerConfigFile::changed:
88 * @config: the #TrackerConfigFile.
89 *
90 * the ::changed signal is emitted whenever
91 * the configuration file has changed on disk.
92 **/
93 signals[CHANGED] =
94 g_signal_new ("changed",
95 G_TYPE_FROM_CLASS (klass),
96 G_SIGNAL_RUN_LAST,
97 G_STRUCT_OFFSET (TrackerConfigFileClass, changed),
98 NULL,
99 NULL,
100 g_cclosure_marshal_VOID__VOID,
101 G_TYPE_NONE,
102 0,
103 G_TYPE_NONE);
104
105 g_object_class_install_property (object_class,
106 PROP_DOMAIN,
107 g_param_spec_string ("domain",
108 "Config domain",
109 "The prefix before .cfg for the filename",
110 g_get_application_name (),
111 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
112
113 g_type_class_add_private (object_class, sizeof (TrackerConfigFilePrivate));
114 }
115
116 static void
117 tracker_config_file_init (TrackerConfigFile *file)
118 {
119 file->key_file = g_key_file_new ();
120 }
121
122 static void
123 config_get_property (GObject *object,
124 guint param_id,
125 GValue *value,
126 GParamSpec *pspec)
127 {
128 TrackerConfigFilePrivate *priv;
129
130 priv = TRACKER_CONFIG_FILE_GET_PRIVATE (object);
131
132 switch (param_id) {
133 case PROP_DOMAIN:
134 g_value_set_string (value, priv->domain);
135 break;
136
137 default:
138 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
139 break;
140 };
141 }
142
143 static void
144 config_set_property (GObject *object,
145 guint param_id,
146 const GValue *value,
147 GParamSpec *pspec)
148 {
149 TrackerConfigFilePrivate *priv;
150 const gchar *domain;
151
152 priv = TRACKER_CONFIG_FILE_GET_PRIVATE (object);
153
154 switch (param_id) {
155 case PROP_DOMAIN:
156 g_free (priv->domain);
157 domain = g_value_get_string (value);
158
159 /* Get rid of the "lt-" prefix if any */
160 if (domain != NULL && g_str_has_prefix (domain, "lt-")) {
161 domain += 3;
162 }
163
164 priv->domain = g_strdup (domain);
165 g_object_notify (object, "domain");
166 break;
167
168 default:
169 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
170 break;
171 };
172 }
173
174 static void
175 config_finalize (GObject *object)
176 {
177 TrackerConfigFile *file;
178 TrackerConfigFilePrivate *priv;
179
180 file = TRACKER_CONFIG_FILE (object);
181 priv = TRACKER_CONFIG_FILE_GET_PRIVATE (file);
182
183 if (file->key_file) {
184 g_key_file_free (file->key_file);
185 }
186
187 if (file->monitor) {
188 g_file_monitor_cancel (file->monitor);
189 g_object_unref (file->monitor);
190 }
191
192 if (file->file) {
193 g_object_unref (file->file);
194 }
195
196 g_free (priv->domain);
197
198 (G_OBJECT_CLASS (tracker_config_file_parent_class)->finalize) (object);
199 }
200
201 static void
202 config_constructed (GObject *object)
203 {
204 config_load (TRACKER_CONFIG_FILE (object));
205 }
206
207 static gchar *
208 config_dir_ensure_exists_and_return (void)
209 {
210 gchar *directory;
211
212 directory = g_build_filename (g_get_user_config_dir (),
213 "tracker",
214 NULL);
215
216 if (!g_file_test (directory, G_FILE_TEST_EXISTS)) {
217 g_print ("Creating config directory:'%s'\n", directory);
218
219 if (g_mkdir_with_parents (directory, 0700) == -1) {
220 g_critical ("Could not create configuration directory");
221 g_free (directory);
222 return NULL;
223 }
224 }
225
226 return directory;
227 }
228
229 static void
230 config_changed_cb (GFileMonitor *monitor,
231 GFile *this_file,
232 GFile *other_file,
233 GFileMonitorEvent event_type,
234 gpointer user_data)
235 {
236 TrackerConfigFile *file;
237 gchar *filename;
238 GTimeVal time_now;
239 static GTimeVal time_last = { 0 };
240
241 file = TRACKER_CONFIG_FILE (user_data);
242
243 /* Do we recreate if the file is deleted? */
244
245 /* Don't emit multiple signals for the same edit. */
246 g_get_current_time (&time_now);
247
248 switch (event_type) {
249 case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
250 case G_FILE_MONITOR_EVENT_CREATED:
251 if ((time_now.tv_sec - time_last.tv_sec) < 1) {
252 return;
253 }
254
255 time_last = time_now;
256
257 file->file_exists = TRUE;
258
259 filename = g_file_get_path (this_file);
260 #ifdef CONFIG_ENABLE_TRACE
261 g_message ("Config file changed:'%s', reloading settings..., event:%d",
262 filename, event_type);
263 #endif /* CONFIG_ENABLE_TRACE */
264 g_free (filename);
265
266 config_load (file);
267
268 g_signal_emit (file, signals[CHANGED], 0);
269 break;
270
271 case G_FILE_MONITOR_EVENT_DELETED:
272 file->file_exists = FALSE;
273 break;
274
275 case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
276 case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
277 case G_FILE_MONITOR_EVENT_UNMOUNTED:
278 file->file_exists = TRUE;
279
280 default:
281 break;
282 }
283 }
284
285 static void
286 config_load (TrackerConfigFile *file)
287 {
288 TrackerConfigFilePrivate *priv;
289 GError *error = NULL;
290 gchar *basename;
291 gchar *filename;
292 gchar *directory;
293
294 /* Check we have a config file and if not, create it based on
295 * the default settings.
296 */
297 directory = config_dir_ensure_exists_and_return ();
298 if (!directory) {
299 return;
300 }
301
302 priv = TRACKER_CONFIG_FILE_GET_PRIVATE (file);
303
304 basename = g_strdup_printf ("%s.cfg", priv->domain);
305 filename = g_build_filename (directory, basename, NULL);
306 g_free (basename);
307 g_free (directory);
308
309 /* Add file monitoring for changes */
310 if (!file->file) {
311 file->file = g_file_new_for_path (filename);
312 }
313
314 if (!file->monitor) {
315 file->monitor = g_file_monitor_file (file->file,
316 G_FILE_MONITOR_NONE,
317 NULL,
318 NULL);
319
320 g_signal_connect (file->monitor, "changed",
321 G_CALLBACK (config_changed_cb),
322 file);
323 }
324
325 /* Load options */
326 g_key_file_load_from_file (file->key_file,
327 filename,
328 G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
329 &error);
330
331 /* We force an overwrite in cases of error */
332 file->file_exists = error ? FALSE : TRUE;
333
334 if (error) {
335 g_error_free (error);
336 }
337
338 g_free (filename);
339 }
340
341 static gboolean
342 config_save (TrackerConfigFile *file)
343 {
344 GError *error = NULL;
345 gchar *filename;
346 gchar *data;
347 gsize size;
348
349 if (!file->key_file) {
350 g_critical ("Could not save config, GKeyFile was NULL, has the config been loaded?");
351
352 return FALSE;
353 }
354
355 #ifdef CONFIG_ENABLE_TRACE
356 g_message ("Setting details to GKeyFile object...");
357 #endif /* CONFIG_ENABLE_TRACE */
358
359 /* FIXME: Get to GKeyFile from object properties */
360
361 #ifdef CONFIG_ENABLE_TRACE
362 g_message ("Saving config to disk...");
363 #endif /* CONFIG_ENABLE_TRACE */
364
365 /* Do the actual saving to disk now */
366 data = g_key_file_to_data (file->key_file, &size, &error);
367 if (error) {
368 g_warning ("Could not get config data to write to file, %s",
369 error->message);
370 g_error_free (error);
371
372 return FALSE;
373 }
374
375 filename = g_file_get_path (file->file);
376
377 g_file_set_contents (filename, data, size, &error);
378 g_free (data);
379
380 if (error) {
381 g_warning ("Could not write %" G_GSIZE_FORMAT " bytes to file '%s', %s",
382 size,
383 filename,
384 error->message);
385 g_free (filename);
386 g_error_free (error);
387
388 return FALSE;
389 }
390
391 #ifdef CONFIG_ENABLE_TRACE
392 g_message ("Wrote config to '%s' (%" G_GSIZE_FORMAT " bytes)",
393 filename,
394 size);
395 #endif /* CONFIG_ENABLE_TRACE */
396
397 g_free (filename);
398
399 return TRUE;
400 }
401
402 /**
403 * tracker_config_file_save:
404 * @config: a #TrackerConfigFile
405 *
406 * Writes the configuration stored in TrackerConfigFile to disk.
407 *
408 * Return value: %TRUE on success, %FALSE otherwise.
409 */
410 gboolean
411 tracker_config_file_save (TrackerConfigFile *file)
412 {
413 g_return_val_if_fail (TRACKER_IS_CONFIG_FILE (file), FALSE);
414
415 return config_save (file);
416 }
417
418 TrackerConfigFile *
419 tracker_config_file_new (void)
420 {
421 return g_object_new (TRACKER_TYPE_CONFIG_FILE,
422 NULL);
423 }
424
425 static gboolean
426 migrate_keyfile_to_settings (TrackerConfigMigrationEntry *entries,
427 TrackerConfigFile *file,
428 GSettings *settings)
429 {
430 gint i;
431
432 for (i = 0; entries[i].type != G_TYPE_INVALID; i++) {
433 if (!g_key_file_has_key (file->key_file,
434 entries[i].file_section,
435 entries[i].file_key,
436 NULL)) {
437 g_settings_reset (settings, entries[i].settings_key);
438 continue;
439 }
440
441 switch (entries[i].type) {
442 case G_TYPE_INT:
443 case G_TYPE_ENUM:
444 {
445 gint val;
446 val = g_key_file_get_integer (file->key_file,
447 entries[i].file_section,
448 entries[i].file_key,
449 NULL);
450
451 if (entries[i].type == G_TYPE_INT) {
452 g_settings_set_int (settings, entries[i].settings_key, val);
453 } else {
454 g_settings_set_enum (settings, entries[i].settings_key, val);
455 }
456 break;
457 }
458 case G_TYPE_BOOLEAN:
459 {
460 gboolean val;
461
462 val = g_key_file_get_boolean (file->key_file,
463 entries[i].file_section,
464 entries[i].file_key,
465 NULL);
466 g_settings_set_boolean (settings, entries[i].settings_key, val);
467 break;
468 }
469 case G_TYPE_POINTER:
470 {
471 gchar **vals;
472
473 vals = g_key_file_get_string_list (file->key_file,
474 entries[i].file_section,
475 entries[i].file_key,
476 NULL, NULL);
477
478 if (vals) {
479 g_settings_set_strv (settings, entries[i].settings_key,
480 (const gchar * const *) vals);
481 g_strfreev (vals);
482 }
483
484 break;
485 }
486 default:
487 g_assert_not_reached ();
488 break;
489 }
490 }
491
492 return TRUE;
493 }
494
495 static void
496 migrate_settings_to_keyfile (TrackerConfigMigrationEntry *entries,
497 GSettings *settings,
498 TrackerConfigFile *file)
499 {
500 gint i;
501
502 #ifdef CONFIG_ENABLE_TRACE
503 g_message ("Storing configuration to Keyfile...");
504 #endif /* CONFIG_ENABLE_TRACE */
505
506 for (i = 0; entries[i].type != G_TYPE_INVALID; i++) {
507 switch (entries[i].type) {
508 case G_TYPE_INT:
509 case G_TYPE_ENUM: {
510 gint val;
511
512 if (entries[i].type == G_TYPE_INT) {
513 val = g_settings_get_int (settings, entries[i].settings_key);
514 } else {
515 val = g_settings_get_enum (settings, entries[i].settings_key);
516 }
517
518 g_key_file_set_integer (file->key_file,
519 entries[i].file_section,
520 entries[i].file_key,
521 val);
522 break;
523 }
524 case G_TYPE_BOOLEAN: {
525 gboolean val;
526
527 val = g_settings_get_boolean (settings, entries[i].settings_key);
528 g_key_file_set_boolean (file->key_file,
529 entries[i].file_section,
530 entries[i].file_key,
531 val);
532 break;
533 }
534 case G_TYPE_POINTER: {
535 gchar **vals;
536
537 vals = g_settings_get_strv (settings, entries[i].settings_key);
538
539 if (vals) {
540 g_key_file_set_string_list (file->key_file,
541 entries[i].file_section,
542 entries[i].file_key,
543 (const gchar * const *)vals,
544 g_strv_length (vals));
545 g_strfreev (vals);
546 }
547
548 break;
549 }
550 default:
551 g_assert_not_reached ();
552 break;
553 }
554 }
555 }
556
557 typedef struct {
558 TrackerConfigFile *file;
559 TrackerConfigMigrationEntry *entries;
560 } UnappliedNotifyData;
561
562 static void
563 settings_has_unapplied_notify (GObject *object,
564 GParamSpec *pspec,
565 gpointer user_data)
566 {
567 UnappliedNotifyData *data = user_data;
568
569 if (!g_settings_get_has_unapplied (G_SETTINGS (object))) {
570 /* Dump to config file too */
571 migrate_settings_to_keyfile (data->entries,
572 G_SETTINGS (object),
573 data->file);
574 tracker_config_file_save (data->file);
575 }
576 }
577
578 gboolean
579 tracker_config_file_migrate (TrackerConfigFile *file,
580 GSettings *settings,
581 TrackerConfigMigrationEntry *entries)
582 {
583 g_return_val_if_fail (TRACKER_IS_CONFIG_FILE (file), FALSE);
584
585 if (G_UNLIKELY (g_getenv ("TRACKER_USE_CONFIG_FILES"))) {
586 UnappliedNotifyData *data;
587
588 #ifdef CONFIG_ENABLE_TRACE
589 g_message ("Using config file, not GSettings");
590 #endif /* CONFIG_ENABLE_TRACE */
591
592 /* Ensure we have the config file in place */
593 if (!file->file_exists) {
594 migrate_settings_to_keyfile (entries,
595 settings,
596 file);
597 tracker_config_file_save (file);
598 }
599
600 /* Keep the file around, and connect to notify::has-unapplied so
601 * we write back to it when g_settings_apply() is called
602 */
603 data = g_new (UnappliedNotifyData, 1);
604 data->file = g_object_ref (file);
605 data->entries = entries;
606
607 g_signal_connect (settings, "notify::has-unapplied",
608 G_CALLBACK (settings_has_unapplied_notify),
609 data);
610 } else {
611 /* 1. Migrate config to GSettings */
612 #ifdef CONFIG_ENABLE_TRACE
613 g_message ("Using GSettings, not config file");
614 #endif /* CONFIG_ENABLE_TRACE */
615 tracker_config_file_import_to_settings (file, settings, entries);
616
617 /* 2. Delete the old config file now it's migrated */
618 #ifdef CONFIG_ENABLE_TRACE
619 g_message (" Removing old config file");
620 #endif /* CONFIG_ENABLE_TRACE */
621 g_file_delete (file->file, NULL, NULL);
622 }
623
624 return TRUE;
625 }
626
627 gboolean
628 tracker_config_file_import_to_settings (TrackerConfigFile *file,
629 GSettings *settings,
630 TrackerConfigMigrationEntry *entries)
631 {
632 g_return_val_if_fail (TRACKER_IS_CONFIG_FILE (file), FALSE);
633
634 g_message ("Importing config file to GSettings");
635
636 if (file->key_file && file->file_exists) {
637 #ifdef CONFIG_ENABLE_TRACE
638 g_message (" Migrating settings from config file to GSettings");
639 #endif /* CONFIG_ENABLE_TRACE */
640
641 migrate_keyfile_to_settings (entries, file, settings);
642 }
643
644 return TRUE;
645 }