/** * bonobo-config-archiver.c: XML configuration database with an interface to * the XML archiver * * Author: * Dietmar Maurer (dietmar@ximian.com) * Bradford Hovinen * * Copyright 2000, 2001 Ximian, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "bonobo-config-archiver.h" static GtkObjectClass *parent_class = NULL; #define CLASS(o) BONOBO_CONFIG_ARCHIVER_CLASS (GTK_OBJECT(o)->klass) #define PARENT_TYPE BONOBO_CONFIG_DATABASE_TYPE #define FLUSH_INTERVAL 30 /* 30 seconds */ /* Small Bonobo bug here... */ #undef BONOBO_RET_EX #define BONOBO_RET_EX(ev) \ G_STMT_START{ \ if (BONOBO_EX (ev)) \ return; \ }G_STMT_END extern int daytime; static DirEntry * dir_lookup_entry (DirData *dir, char *name, gboolean create) { GSList *l; DirEntry *de; l = dir->entries; while (l) { de = (DirEntry *)l->data; if (!strcmp (de->name, name)) return de; l = l->next; } if (create) { de = g_new0 (DirEntry, 1); de->dir = dir; de->name = g_strdup (name); dir->entries = g_slist_prepend (dir->entries, de); return de; } return NULL; } static DirData * dir_lookup_subdir (DirData *dir, char *name, gboolean create) { GSList *l; DirData *dd; l = dir->subdirs; while (l) { dd = (DirData *)l->data; if (!strcmp (dd->name, name)) return dd; l = l->next; } if (create) { dd = g_new0 (DirData, 1); dd->dir = dir; dd->name = g_strdup (name); dir->subdirs = g_slist_prepend (dir->subdirs, dd); return dd; } return NULL; } static DirData * lookup_dir (DirData *dir, const char *path, gboolean create) { const char *s, *e; char *name; DirData *dd = dir; s = path; while (*s == '/') s++; if (*s == '\0') return dir; if ((e = strchr (s, '/'))) name = g_strndup (s, e - s); else name = g_strdup (s); if ((dd = dir_lookup_subdir (dd, name, create))) { if (e) dd = lookup_dir (dd, e, create); g_free (name); return dd; } else { g_free (name); } return NULL; } static DirEntry * lookup_dir_entry (BonoboConfigArchiver *archiver_db, const char *key, gboolean create) { char *dir_name; char *leaf_name; DirEntry *de; DirData *dd; if ((dir_name = bonobo_config_dir_name (key))) { dd = lookup_dir (archiver_db->dir, dir_name, create); if (dd && !dd->node) { dd->node = xmlNewChild (archiver_db->doc->root, NULL, "section", NULL); xmlSetProp (dd->node, "path", dir_name); } g_free (dir_name); } else { dd = archiver_db->dir; if (!dd->node) dd->node = xmlNewChild (archiver_db->doc->root, NULL, "section", NULL); } if (!dd) return NULL; if (!(leaf_name = bonobo_config_leaf_name (key))) return NULL; de = dir_lookup_entry (dd, leaf_name, create); g_free (leaf_name); return de; } static CORBA_any * real_get_value (BonoboConfigDatabase *db, const CORBA_char *key, const CORBA_char *locale, CORBA_Environment *ev) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (db); DirEntry *de; CORBA_any *value = NULL; de = lookup_dir_entry (archiver_db, key, FALSE); if (!de) { bonobo_exception_set (ev, ex_Bonobo_ConfigDatabase_NotFound); return NULL; } if (de->value) return bonobo_arg_copy (de->value); /* localized value */ if (de->node && de->node->childs && de->node->childs->next) value = bonobo_config_xml_decode_any ((BonoboUINode *)de->node, locale, ev); if (!value) bonobo_exception_set (ev, ex_Bonobo_ConfigDatabase_NotFound); return value; } /* Produce a new, dynamically allocated string with the OAF IID of the listener * associated with the given backend id */ static gchar * get_listener_oafiid (const gchar *backend_id) { gchar *oafiid, *tmp, *tmp1; tmp = g_strdup (backend_id); if ((tmp1 = strstr (tmp, "-properties")) != NULL) *tmp1 = '\0'; oafiid = g_strconcat ("OAFIID:Bonobo_Listener_Config_", tmp, NULL); g_free (tmp); return oafiid; } static void real_sync (BonoboConfigDatabase *db, CORBA_Environment *ev) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (db); BonoboArg *arg; gchar *listener_oafiid; Bonobo_Listener listener; if (!db->writeable) return; /* FIXME: This will not work correctly in the pathlogical case that two * ConfigArchiver objects sync at almost exactly the same time. */ archiver_db->is_up_to_date = TRUE; location_client_store_xml (archiver_db->location, archiver_db->backend_id, archiver_db->doc, ConfigArchiver_STORE_MASK_PREVIOUS, ev); BONOBO_RET_EX (ev); /* Try to find a listener to apply the settings. If we can't, don't * worry about it */ listener_oafiid = get_listener_oafiid (archiver_db->backend_id); listener = bonobo_get_object (listener_oafiid, "IDL:Bonobo/Listener:1.0", ev); g_free (listener_oafiid); if (BONOBO_EX (ev)) { listener = CORBA_OBJECT_NIL; CORBA_exception_init (ev); } arg = bonobo_arg_new (BONOBO_ARG_NULL); bonobo_event_source_notify_listeners (archiver_db->es, "Bonobo/ConfigDatabase:sync", arg, ev); bonobo_arg_release (arg); if (listener != CORBA_OBJECT_NIL) bonobo_object_release_unref (listener, NULL); } static void notify_listeners (BonoboConfigArchiver *archiver_db, const char *key, const CORBA_any *value) { CORBA_Environment ev; char *dir_name; char *leaf_name; char *ename; if (GTK_OBJECT_DESTROYED (archiver_db)) return; if (!key) return; CORBA_exception_init (&ev); ename = g_strconcat ("Bonobo/Property:change:", key, NULL); bonobo_event_source_notify_listeners (archiver_db->es, ename, value, &ev); g_free (ename); if (!(dir_name = bonobo_config_dir_name (key))) dir_name = g_strdup (""); if (!(leaf_name = bonobo_config_leaf_name (key))) leaf_name = g_strdup (""); ename = g_strconcat ("Bonobo/ConfigDatabase:change", dir_name, ":", leaf_name, NULL); bonobo_event_source_notify_listeners (archiver_db->es, ename, value, &ev); CORBA_exception_free (&ev); g_free (ename); g_free (dir_name); g_free (leaf_name); } static void real_set_value (BonoboConfigDatabase *db, const CORBA_char *key, const CORBA_any *value, CORBA_Environment *ev) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (db); DirEntry *de; char *name; de = lookup_dir_entry (archiver_db, key, TRUE); if (de->value) CORBA_free (de->value); de->value = bonobo_arg_copy (value); if (de->node) { xmlUnlinkNode (de->node); xmlFreeNode (de->node); } name = bonobo_config_leaf_name (key); de->node = (xmlNodePtr) bonobo_config_xml_encode_any (value, name, ev); g_free (name); bonobo_ui_node_add_child ((BonoboUINode *)de->dir->node, (BonoboUINode *)de->node); notify_listeners (archiver_db, key, value); } static Bonobo_KeyList * real_list_dirs (BonoboConfigDatabase *db, const CORBA_char *dir, CORBA_Environment *ev) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (db); Bonobo_KeyList *key_list; DirData *dd, *sub; GSList *l; int len; key_list = Bonobo_KeyList__alloc (); key_list->_length = 0; if (!(dd = lookup_dir (archiver_db->dir, dir, FALSE))) return key_list; if (!(len = g_slist_length (dd->subdirs))) return key_list; key_list->_buffer = CORBA_sequence_CORBA_string_allocbuf (len); CORBA_sequence_set_release (key_list, TRUE); for (l = dd->subdirs; l != NULL; l = l->next) { sub = (DirData *)l->data; key_list->_buffer [key_list->_length] = CORBA_string_dup (sub->name); key_list->_length++; } return key_list; } static Bonobo_KeyList * real_list_keys (BonoboConfigDatabase *db, const CORBA_char *dir, CORBA_Environment *ev) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (db); Bonobo_KeyList *key_list; DirData *dd; DirEntry *de; GSList *l; int len; key_list = Bonobo_KeyList__alloc (); key_list->_length = 0; if (!(dd = lookup_dir (archiver_db->dir, dir, FALSE))) return key_list; if (!(len = g_slist_length (dd->entries))) return key_list; key_list->_buffer = CORBA_sequence_CORBA_string_allocbuf (len); CORBA_sequence_set_release (key_list, TRUE); for (l = dd->entries; l != NULL; l = l->next) { de = (DirEntry *)l->data; key_list->_buffer [key_list->_length] = CORBA_string_dup (de->name); key_list->_length++; } return key_list; } static CORBA_boolean real_dir_exists (BonoboConfigDatabase *db, const CORBA_char *dir, CORBA_Environment *ev) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (db); if (lookup_dir (archiver_db->dir, dir, FALSE)) return TRUE; return FALSE; } static void delete_dir_entry (DirEntry *de) { CORBA_free (de->value); if (de->node) { xmlUnlinkNode (de->node); xmlFreeNode (de->node); } g_free (de->name); g_free (de); } static void real_remove_value (BonoboConfigDatabase *db, const CORBA_char *key, CORBA_Environment *ev) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (db); DirEntry *de; if (!(de = lookup_dir_entry (archiver_db, key, FALSE))) return; de->dir->entries = g_slist_remove (de->dir->entries, de); delete_dir_entry (de); } static void delete_dir_data (DirData *dd, gboolean is_root) { GSList *l; for (l = dd->subdirs; l; l = l->next) delete_dir_data ((DirData *)l->data, FALSE); g_slist_free (dd->subdirs); dd->subdirs = NULL; for (l = dd->entries; l; l = l->next) delete_dir_entry ((DirEntry *)l->data); g_slist_free (dd->entries); dd->entries = NULL; if (!is_root) { g_free (dd->name); if (dd->node) { xmlUnlinkNode (dd->node); xmlFreeNode (dd->node); } g_free (dd); } } static void real_remove_dir (BonoboConfigDatabase *db, const CORBA_char *dir, CORBA_Environment *ev) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (db); DirData *dd; if (!(dd = lookup_dir (archiver_db->dir, dir, FALSE))) return; if (dd != archiver_db->dir && dd->dir) dd->dir->subdirs = g_slist_remove (dd->dir->subdirs, dd); delete_dir_data (dd, dd == archiver_db->dir); } static void pb_get_fn (BonoboPropertyBag *bag, BonoboArg *arg, guint arg_id, CORBA_Environment *ev, gpointer user_data) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (user_data); time_t val; val = ConfigArchiver_Location_getModificationTime (archiver_db->location, archiver_db->backend_id, ev); BONOBO_ARG_SET_GENERAL (arg, val, TC_ulonglong, CORBA_unsigned_long_long, NULL); } static void pb_set_fn (BonoboPropertyBag *bag, const BonoboArg *arg, guint arg_id, CORBA_Environment *ev, gpointer user_data) { g_assert_not_reached (); } static void bonobo_config_archiver_destroy (GtkObject *object) { BonoboConfigArchiver *archiver_db = BONOBO_CONFIG_ARCHIVER (object); CORBA_Environment ev; DEBUG_MSG ("Enter"); CORBA_exception_init (&ev); if (archiver_db->moniker != NULL) { bonobo_url_unregister ("BONOBO_CONF:ARCHIVER", archiver_db->moniker, &ev); g_free (archiver_db->moniker); if (BONOBO_EX (&ev)) { g_critical ("Could not unregister the archiver URL"); CORBA_exception_init (&ev); } } if (archiver_db->listener_id != 0) { bonobo_event_source_client_remove_listener (archiver_db->location, archiver_db->listener_id, &ev); if (BONOBO_EX (&ev)) g_critical ("Could not remove the rollback data listener"); } CORBA_exception_free (&ev); if (archiver_db->doc != NULL) { delete_dir_data (archiver_db->dir, TRUE); archiver_db->dir = NULL; xmlFreeDoc (archiver_db->doc); } if (archiver_db->filename != NULL) g_free (archiver_db->filename); if (archiver_db->fp != NULL) fclose (archiver_db->fp); if (archiver_db->location != CORBA_OBJECT_NIL) bonobo_object_release_unref (archiver_db->location, NULL); if (archiver_db->archive != CORBA_OBJECT_NIL) bonobo_object_release_unref (archiver_db->archive, NULL); parent_class->destroy (object); DEBUG_MSG ("Exit"); } static void bonobo_config_archiver_class_init (BonoboConfigDatabaseClass *class) { GtkObjectClass *object_class = (GtkObjectClass *) class; BonoboConfigDatabaseClass *cd_class; parent_class = gtk_type_class (PARENT_TYPE); object_class->destroy = bonobo_config_archiver_destroy; cd_class = BONOBO_CONFIG_DATABASE_CLASS (class); cd_class->get_value = real_get_value; cd_class->set_value = real_set_value; cd_class->list_dirs = real_list_dirs; cd_class->list_keys = real_list_keys; cd_class->dir_exists = real_dir_exists; cd_class->remove_value = real_remove_value; cd_class->remove_dir = real_remove_dir; cd_class->sync = real_sync; } static void bonobo_config_archiver_init (BonoboConfigArchiver *archiver_db) { archiver_db->dir = g_new0 (DirData, 1); /* This will always be writeable */ BONOBO_CONFIG_DATABASE (archiver_db)->writeable = TRUE; } BONOBO_X_TYPE_FUNC (BonoboConfigArchiver, PARENT_TYPE, bonobo_config_archiver); static void read_section (DirData *dd) { CORBA_Environment ev; DirEntry *de; xmlNodePtr s; gchar *name; CORBA_exception_init (&ev); s = dd->node->childs; while (s) { if (s->type == XML_ELEMENT_NODE && !strcmp (s->name, "entry") && (name = xmlGetProp(s, "name"))) { de = dir_lookup_entry (dd, name, TRUE); de->node = s; /* we only decode it if it is a single value, * multiple (localized) values are decoded in the * get_value function */ if (!(s->childs && s->childs->next)) de->value = bonobo_config_xml_decode_any ((BonoboUINode *)s, NULL, &ev); xmlFree (name); } s = s->next; } CORBA_exception_free (&ev); } static void fill_cache (BonoboConfigArchiver *archiver_db) { xmlNodePtr n; gchar *path; DirData *dd; n = archiver_db->doc->root->childs; while (n) { if (n->type == XML_ELEMENT_NODE && !strcmp (n->name, "section")) { path = xmlGetProp(n, "path"); if (!path || !strcmp (path, "")) { dd = archiver_db->dir; } else dd = lookup_dir (archiver_db->dir, path, TRUE); if (!dd->node) dd->node = n; read_section (dd); xmlFree (path); } n = n->next; } } /* parse_name * * Given a moniker with a backend id and (possibly) a location id encoded * therein, parse out the backend id and the location id and set the pointers * given to them. * * FIXME: Is this encoding really the way we want to do this? Ask Dietmar and * Michael. */ static gboolean parse_name (const gchar *name, gchar **backend_id, gchar **location, struct tm **date) { gchar *e, *e1, *time_str = NULL; *date = NULL; if (name[0] == '[') { e = strchr (name + 1, '|'); if (e != NULL) { *location = g_strndup (name + 1, e - (name + 1)); e1 = strchr (e + 1, ']'); if (e1 != NULL) { time_str = g_strndup (e + 1, e1 - (e + 1)); *date = parse_date (time_str); g_free (time_str); } *backend_id = g_strdup (e1 + 1); } else { e = strchr (name + 1, ']'); if (e != NULL) *location = g_strndup (name + 1, e - (name + 1)); else return FALSE; *backend_id = g_strdup (e + 1); } } else { *backend_id = g_strdup (name); *location = NULL; } if (*location != NULL && **location == '\0') { g_free (*location); *location = NULL; } return TRUE; } static void new_rollback_cb (BonoboListener *listener, gchar *event_name, CORBA_any *any, CORBA_Environment *ev, BonoboConfigArchiver *archiver_db) { BonoboArg *arg; if (archiver_db->is_up_to_date) { archiver_db->is_up_to_date = FALSE; return; } if (archiver_db->dir != NULL) { delete_dir_data (archiver_db->dir, TRUE); g_free (archiver_db->dir->name); g_free (archiver_db->dir); archiver_db->dir = g_new0 (DirData, 1); } if (archiver_db->doc != NULL) xmlFreeDoc (archiver_db->doc); archiver_db->doc = location_client_load_rollback_data (archiver_db->location, NULL, 0, archiver_db->backend_id, TRUE, ev); if (archiver_db->doc == NULL) g_critical ("Could not load new rollback data"); else fill_cache (archiver_db); arg = bonobo_arg_new (BONOBO_ARG_NULL); bonobo_event_source_notify_listeners (archiver_db->es, "Bonobo/ConfigDatabase:sync", arg, ev); bonobo_arg_release (arg); } Bonobo_ConfigDatabase bonobo_config_archiver_new (Bonobo_Moniker parent, const Bonobo_ResolveOptions *options, const char *moniker, CORBA_Environment *ev) { BonoboConfigArchiver *archiver_db; Bonobo_ConfigDatabase db; ConfigArchiver_Archive archive; ConfigArchiver_Location location; gchar *moniker_tmp; gchar *backend_id; gchar *location_id; struct tm *date; g_return_val_if_fail (backend_id != NULL, NULL); /* Check the Bonobo URL database to see if this archiver database has * already been created, and return it if it has */ moniker_tmp = g_strdup (moniker); db = bonobo_url_lookup ("BONOBO_CONF:ARCHIVER", moniker_tmp, ev); g_free (moniker_tmp); if (BONOBO_EX (ev)) { db = CORBA_OBJECT_NIL; CORBA_exception_init (ev); } if (db != CORBA_OBJECT_NIL) return bonobo_object_dup_ref (db, NULL); /* Parse out the backend id, location id, and rollback date from the * moniker given */ if (parse_name (moniker, &backend_id, &location_id, &date) < 0) { EX_SET_NOT_FOUND (ev); return CORBA_OBJECT_NIL; } /* Resolve the parent archive and the location */ archive = Bonobo_Moniker_resolve (parent, options, "IDL:ConfigArchiver/Archive:1.0", ev); if (BONOBO_EX (ev) || archive == CORBA_OBJECT_NIL) { g_free (location_id); g_free (date); return CORBA_OBJECT_NIL; } if (location_id == NULL || *location_id == '\0') location = ConfigArchiver_Archive__get_currentLocation (archive, ev); else location = ConfigArchiver_Archive_getLocation (archive, location_id, ev); g_free (location_id); if (location == CORBA_OBJECT_NIL) { g_free (date); bonobo_object_release_unref (archive, NULL); return CORBA_OBJECT_NIL; } /* Construct the database object proper and fill in its values */ if ((archiver_db = gtk_type_new (BONOBO_CONFIG_ARCHIVER_TYPE)) == NULL) { g_free (date); return CORBA_OBJECT_NIL; } archiver_db->backend_id = backend_id; archiver_db->moniker = g_strdup (moniker); archiver_db->archive = archive; archiver_db->location = location; /* Load the XML data, or use the defaults file if none are present */ archiver_db->doc = location_client_load_rollback_data (archiver_db->location, date, 0, archiver_db->backend_id, TRUE, ev); g_free (date); if (BONOBO_EX (ev) || archiver_db->doc == NULL) { gchar *filename; filename = g_strconcat (GNOMECC_DEFAULTS_DIR "/", archiver_db->backend_id, ".xml", NULL); archiver_db->doc = xmlParseFile (filename); g_free (filename); if (archiver_db->doc == NULL) { bonobo_object_release_unref (archiver_db->location, NULL); bonobo_object_release_unref (archiver_db->archive, NULL); bonobo_object_unref (BONOBO_OBJECT (archiver_db)); return CORBA_OBJECT_NIL; } CORBA_exception_init (ev); } /* Load data from the XML file */ if (archiver_db->doc->root == NULL) archiver_db->doc->root = xmlNewDocNode (archiver_db->doc, NULL, "bonobo-config", NULL); if (strcmp (archiver_db->doc->root->name, "bonobo-config")) { xmlFreeDoc (archiver_db->doc); archiver_db->doc = xmlNewDoc("1.0"); archiver_db->doc->root = xmlNewDocNode (archiver_db->doc, NULL, "bonobo-config", NULL); } fill_cache (archiver_db); /* Construct the associated property bag and event source */ #if 0 archiver_db->es = bonobo_event_source_new (); bonobo_object_add_interface (BONOBO_OBJECT (archiver_db), BONOBO_OBJECT (archiver_db->es)); #endif archiver_db->pb = bonobo_property_bag_new (pb_get_fn, pb_set_fn, archiver_db); bonobo_object_add_interface (BONOBO_OBJECT (archiver_db), BONOBO_OBJECT (archiver_db->pb)); archiver_db->es = archiver_db->pb->es; bonobo_property_bag_add (archiver_db->pb, "last_modified", 1, TC_ulonglong, NULL, "Date (time_t) of modification", BONOBO_PROPERTY_READABLE); /* Listen for events pertaining to new rollback data */ if (date == NULL && location_id == NULL) archiver_db->listener_id = bonobo_event_source_client_add_listener (location, (BonoboListenerCallbackFn) new_rollback_cb, "ConfigArchiver/Location:newRollbackData", ev, archiver_db); else archiver_db->listener_id = 0; /* Prepare to return the database object */ db = CORBA_Object_duplicate (BONOBO_OBJREF (archiver_db), NULL); moniker_tmp = g_strdup (moniker); bonobo_url_register ("BONOBO_CONF:ARCHIVER", moniker_tmp, NULL, db, ev); g_free (moniker_tmp); return db; }