tracker-0.16.2/src/libtracker-common/tracker-config-file.c

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 }