/* * Copyright (C) 2003 Marco Pesenti Gritti * Copyright (C) 2003, 2004 Christian Persch * Copyright (C) 2004 Adam Hooper * Copyright (C) 2005 Crispin Flowerday * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id$ */ #include "config.h" #include "ephy-extensions-manager.h" #include "ephy-loader.h" #include "ephy-shlib-loader.h" #include "ephy-node-db.h" #include "ephy-shell.h" #include "eel-gconf-extensions.h" #include "ephy-file-helpers.h" #include "ephy-object-helpers.h" #include "ephy-debug.h" #include #include #include #include #include #include #include #include #ifdef ENABLE_PYTHON #include "ephy-python-extension.h" #include "ephy-python-loader.h" #endif #define CONF_LOADED_EXTENSIONS "/apps/epiphany/general/active_extensions" #define DOT_INI ".ephy-extension" #define EPHY_EXTENSIONS_MANAGER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_EXTENSIONS_MANAGER, EphyExtensionsManagerPrivate)) struct _EphyExtensionsManagerPrivate { gboolean initialised; GList *data; GList *factories; GList *extensions; GList *dir_monitors; GList *windows; guint active_extensions_notifier_id; }; typedef struct { EphyExtensionInfo info; guint version; gboolean load_failed; char *loader_type; GData *loader_attributes; EphyLoader *loader; /* NULL if never loaded */ GObject *extension; /* NULL if unloaded */ } ExtensionInfo; typedef struct { char *type; EphyLoader *loader; } LoaderInfo; enum { CHANGED, ADDED, REMOVED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static GObjectClass *parent_class = NULL; static void ephy_extensions_manager_class_init (EphyExtensionsManagerClass *klass); static void ephy_extensions_manager_iface_init (EphyExtensionIface *iface); static void ephy_extensions_manager_init (EphyExtensionsManager *manager); GType ephy_extensions_manager_get_type (void) { static GType type = 0; if (G_UNLIKELY (type == 0)) { static const GTypeInfo our_info = { sizeof (EphyExtensionsManagerClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) ephy_extensions_manager_class_init, NULL, NULL, /* class_data */ sizeof (EphyExtensionsManager), 0, /* n_preallocs */ (GInstanceInitFunc) ephy_extensions_manager_init }; static const GInterfaceInfo extension_info = { (GInterfaceInitFunc) ephy_extensions_manager_iface_init, NULL, NULL }; type = g_type_register_static (G_TYPE_OBJECT, "EphyExtensionsManager", &our_info, 0); g_type_add_interface_static (type, EPHY_TYPE_EXTENSION, &extension_info); } return type; } /** * ephy_extensions_manager_load: * @manager: an #EphyExtensionsManager * @name: identifier of the extension to load * * Loads the @name extension. **/ void ephy_extensions_manager_load (EphyExtensionsManager *manager, const char *identifier) { GSList *gconf_exts; g_return_if_fail (EPHY_IS_EXTENSIONS_MANAGER (manager)); g_return_if_fail (identifier != NULL); LOG ("Adding '%s' to extensions", identifier); gconf_exts = eel_gconf_get_string_list (CONF_LOADED_EXTENSIONS); if (!g_slist_find_custom (gconf_exts, identifier, (GCompareFunc) strcmp)) { gconf_exts = g_slist_prepend (gconf_exts, g_strdup (identifier)); eel_gconf_set_string_list (CONF_LOADED_EXTENSIONS, gconf_exts); } g_slist_foreach (gconf_exts, (GFunc) g_free, NULL); g_slist_free (gconf_exts); } /** * ephy_extensions_manager_unload: * @manager: an #EphyExtensionsManager * @name: filename of extension to unload, minus "lib" and "extension.so" * * Unloads the extension specified by @name. * * The extension with the same filename can afterwards be reloaded. However, * if any GTypes within the extension have changed parent types, Epiphany must * be restarted. **/ void ephy_extensions_manager_unload (EphyExtensionsManager *manager, const char *identifier) { GSList *gconf_exts; GSList *l; g_return_if_fail (EPHY_IS_EXTENSIONS_MANAGER (manager)); g_return_if_fail (identifier != NULL); LOG ("Removing '%s' from extensions", identifier); gconf_exts = eel_gconf_get_string_list (CONF_LOADED_EXTENSIONS); l = g_slist_find_custom (gconf_exts, identifier, (GCompareFunc) strcmp); if (l != NULL) { gconf_exts = g_slist_remove_link (gconf_exts, l); g_free (l->data); g_slist_free_1 (l); eel_gconf_set_string_list (CONF_LOADED_EXTENSIONS, gconf_exts); } g_slist_foreach (gconf_exts, (GFunc) g_free, NULL); g_slist_free (gconf_exts); } /** * ephy_extensions_manager_register: * @manager: an #EphyExtensionsManager * @object: an Extension * * Registers @object with the extensions manager. @object must implement the * #EphyExtension interface. **/ void ephy_extensions_manager_register (EphyExtensionsManager *manager, GObject *object) { g_return_if_fail (EPHY_IS_EXTENSIONS_MANAGER (manager)); g_return_if_fail (EPHY_IS_EXTENSION (object)); manager->priv->extensions = g_list_prepend (manager->priv->extensions, g_object_ref (object)); } /** * ephy_extensions_manager_get_extensions: * @manager: an #EphyExtensionsManager * * Returns the list of known extensions. * * Returns: a list of #EphyExtensionInfo **/ GList * ephy_extensions_manager_get_extensions (EphyExtensionsManager *manager) { return g_list_copy (manager->priv->data); } static void free_extension_info (ExtensionInfo *info) { EphyExtensionInfo *einfo = (EphyExtensionInfo *) info; g_free (einfo->identifier); g_free (einfo->name); g_free (einfo->description); g_list_foreach (einfo->authors, (GFunc) g_free, NULL); g_list_free (einfo->authors); g_free (einfo->url); g_free (info->loader_type); g_datalist_clear (&info->loader_attributes); if (info->extension != NULL) { g_return_if_fail (info->loader != NULL); ephy_loader_release_object (info->loader, info->extension); } if (info->loader != NULL) { g_object_unref (info->loader); } g_free (info); } static void free_loader_info (LoaderInfo *info) { g_free (info->type); g_object_unref (info->loader); g_free (info); } static int find_extension_info (const ExtensionInfo *info, const char *identifier) { return strcmp (info->info.identifier, identifier); } typedef struct { xmlChar *string; guint match; } LocalisedString; static const char * const *languages = NULL; static void assign_localised_string (xmlTextReaderPtr reader, LocalisedString *string) { const xmlChar *value, *lang; guint i; lang = xmlTextReaderConstXmlLang (reader); value = xmlTextReaderConstValue (reader); if (G_UNLIKELY (lang == NULL)) { /* languages always has "C" in it, so make sure we get a match */ lang = (const xmlChar *) "C"; } for (i = 0; languages[i] != NULL && i < string->match; i++) { if (lang != NULL && strcmp ((const char *) lang, languages[i]) == 0) { xmlFree (string->string); string->string = xmlStrdup (value); string->match = i; break; } } } typedef enum { STATE_START, STATE_STOP, STATE_ERROR, STATE_EXTENSION, STATE_NAME, STATE_DESCRIPTION, STATE_VERSION, STATE_AUTHOR, STATE_URL, STATE_GETTEXT_DOMAIN, STATE_LOCALE_DIRECTORY, STATE_LOADER, STATE_LOADER_ATTRIBUTE, STATE_LOAD_DEFERRED, } ParserState; static void ephy_extensions_manager_load_xml_string (EphyExtensionsManager *manager, const char *identifier, /* const */ char *xml) { xmlDocPtr doc; xmlTextReaderPtr reader; ParserState state = STATE_START; GQuark attr_quark = 0; EphyExtensionInfo *einfo; ExtensionInfo *info; int ret; LocalisedString description = { NULL, G_MAXUINT }; LocalisedString name = { NULL, G_MAXUINT }; LOG ("Loading XML description file for '%s'", identifier); if (g_list_find_custom (manager->priv->data, identifier, (GCompareFunc) find_extension_info) != NULL) { g_warning ("Extension description for '%s' already read!", identifier); return; } /* FIXME: Ideally we'd put the schema validator in the reader. libxml2 * doesn't seem to support that at this point in time, so we've got to * put the schema validation on the Doc Tree and then pass that to the * reader. (maybe switch to RelaxNG?) */ doc = xmlParseDoc ((xmlChar *) xml); if (doc == NULL) { g_warning ("Couldn't read '%s' data\n", identifier); return; } /* Now parse it */ reader = xmlReaderWalker (doc); g_return_if_fail (reader != NULL); info = g_new0 (ExtensionInfo, 1); einfo = (EphyExtensionInfo *) info; einfo->identifier = g_strdup (identifier); g_datalist_init (&info->loader_attributes); ret = xmlTextReaderRead (reader); while (ret == 1) { const xmlChar *tag; xmlReaderTypes type; tag = xmlTextReaderConstName (reader); type = xmlTextReaderNodeType (reader); if (state == STATE_LOADER && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "attribute")) { xmlChar *name; state = STATE_LOADER_ATTRIBUTE; name = xmlTextReaderGetAttribute (reader, (const xmlChar *) "name"); attr_quark = g_quark_from_string ((const char *) name); xmlFree (name); } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "author")) { state = STATE_AUTHOR; } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "description")) { state = STATE_DESCRIPTION; } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "gettext-domain")) { state = STATE_GETTEXT_DOMAIN; } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "load-deferred")) { state = STATE_LOAD_DEFERRED; } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "locale-directory")) { state = STATE_LOCALE_DIRECTORY; } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "name")) { state = STATE_NAME; } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "url")) { state = STATE_URL; } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "version")) { state = STATE_VERSION; } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "loader")) { xmlChar * attr; state = STATE_LOADER; attr = xmlTextReaderGetAttribute (reader, (const xmlChar *) "type"); info->loader_type = g_strdup ((char*)attr); xmlFree (attr); } else if (state == STATE_LOADER_ATTRIBUTE && type == XML_READER_TYPE_TEXT && attr_quark != 0) { const xmlChar *value; value = xmlTextReaderConstValue (reader); g_datalist_id_set_data_full (&info->loader_attributes, attr_quark, g_strdup ((char*)value), (GDestroyNotify) g_free); attr_quark = 0; } else if (state == STATE_LOADER_ATTRIBUTE && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "attribute")) { state = STATE_LOADER; } else if (state == STATE_AUTHOR && type == XML_READER_TYPE_TEXT) { const xmlChar *attr; attr = xmlTextReaderConstValue (reader); einfo->authors = g_list_prepend (einfo->authors, g_strdup ((char*)attr)); } else if (state == STATE_DESCRIPTION && type == XML_READER_TYPE_TEXT) { assign_localised_string (reader, &description); } else if (type == XML_READER_TYPE_TEXT && (state == STATE_GETTEXT_DOMAIN || state == STATE_LOAD_DEFERRED || state == STATE_LOCALE_DIRECTORY)) { /* not supported anymore */ } else if (state == STATE_NAME && type == XML_READER_TYPE_TEXT) { assign_localised_string (reader, &name); } else if (state == STATE_VERSION && type == XML_READER_TYPE_TEXT) { info->version = (guint) strtol ((const char *) xmlTextReaderConstValue (reader), NULL, 10); } else if (state == STATE_URL && type == XML_READER_TYPE_TEXT) { const xmlChar *attr; attr = xmlTextReaderConstValue (reader); einfo->url = g_strdup ((char*)attr); } else if (state == STATE_AUTHOR && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "author")) { state = STATE_EXTENSION; } else if (state == STATE_DESCRIPTION && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "description")) { state = STATE_EXTENSION; } else if (state == STATE_GETTEXT_DOMAIN && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "gettext-domain")) { state = STATE_EXTENSION; } else if (state == STATE_LOCALE_DIRECTORY && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "locale-directory")) { state = STATE_EXTENSION; } else if (state == STATE_LOADER && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "loader")) { state = STATE_EXTENSION; } else if (state == STATE_NAME && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "name")) { state = STATE_EXTENSION; } else if (state == STATE_URL && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "url")) { state = STATE_EXTENSION; } else if (state == STATE_VERSION && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "version")) { state = STATE_EXTENSION; } else if (type == XML_READER_TYPE_SIGNIFICANT_WHITESPACE || type == XML_READER_TYPE_WHITESPACE || type == XML_READER_TYPE_TEXT) { /* eat it */ } else if (state == STATE_START && type == XML_READER_TYPE_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "extension")) { state = STATE_EXTENSION; } else if (state == STATE_EXTENSION && type == XML_READER_TYPE_END_ELEMENT && xmlStrEqual (tag, (const xmlChar *) "extension")) { state = STATE_STOP; } else { const xmlChar *content; content = xmlTextReaderConstValue (reader); g_warning ("tag '%s' of type %d in state %d with content '%s' was unexpected!", tag, type, state, content ? (char *) content : "(null)"); state = STATE_ERROR; break; } ret = xmlTextReaderRead (reader); } xmlFreeTextReader (reader); xmlFreeDoc (doc); /* assign localised strings */ einfo->description = (char *)description.string; einfo->name = (char *)name.string; /* Reverse authors list */ einfo->authors = g_list_reverse (einfo->authors); /* sanity check */ if (ret < 0 || state != STATE_STOP || einfo->name == NULL || einfo->description == NULL || info->loader_type == NULL || info->loader_type[0] == '\0') { free_extension_info (info); return; } manager->priv->data = g_list_prepend (manager->priv->data, info); g_signal_emit (manager, signals[ADDED], 0, info); } static void ephy_extensions_manager_load_ini_string (EphyExtensionsManager *manager, const char * identifier, const char * contents) { ExtensionInfo *info; EphyExtensionInfo *einfo; gchar ** list; int i; GKeyFile *key_file; GError *err = NULL; LOG ("Loading INI description file for '%s'", identifier); if (g_list_find_custom (manager->priv->data, identifier, (GCompareFunc) find_extension_info) != NULL) { g_warning ("Extension description for '%s' already read!", identifier); return; } key_file = g_key_file_new (); if (g_key_file_load_from_data (key_file, contents, strlen (contents), G_KEY_FILE_NONE, &err) == FALSE) { g_warning ("Could load key file for '%s': '%s'", identifier, err->message); g_error_free (err); g_key_file_free (key_file); return; } if (g_key_file_has_group (key_file, "Epiphany Extension") == FALSE || g_key_file_has_group (key_file, "Loader") == FALSE) { g_warning ("Invalid extension description file for '%s'; " "missing 'Epiphany Extension' or 'Loader' group", identifier); g_key_file_free (key_file); return; } info = g_new0 (ExtensionInfo, 1); einfo = (EphyExtensionInfo *) info; einfo->identifier = g_strdup (identifier); g_datalist_init (&info->loader_attributes); einfo->name = g_key_file_get_locale_string (key_file, "Epiphany Extension", "Name", NULL, NULL); einfo->description = g_key_file_get_locale_string (key_file, "Epiphany Extension", "Description", NULL, NULL); einfo->url = g_key_file_get_locale_string (key_file, "Epiphany Extension", "URL", NULL, NULL); list = g_key_file_get_string_list (key_file, "Epiphany Extension", "Authors", NULL, NULL); if (list) { for (i = 0 ; list[i]; i++ ) { einfo->authors = g_list_prepend (einfo->authors, g_strstrip (g_strdup (list[i]))); } g_strfreev (list); } einfo->authors = g_list_reverse (einfo->authors); info->version = g_key_file_get_integer (key_file, "Epiphany Extension", "Version", NULL); /* Load the loader flags */ list = g_key_file_get_keys (key_file, "Loader", NULL, NULL); if (list) { for (i = 0 ; list[i]; i++ ) { char * value; GQuark attr_quark = 0; value = g_key_file_get_string (key_file, "Loader", list[i], NULL); if (strcmp (list[i], "Type") == 0) { info->loader_type = value; continue; } attr_quark = g_quark_from_string (list[i]); g_datalist_id_set_data_full (&info->loader_attributes, attr_quark, value, (GDestroyNotify) g_free); } g_strfreev (list); } g_key_file_free (key_file); /* sanity check */ if (einfo->name == NULL || einfo->description == NULL || info->loader_type == NULL || info->loader_type[0] == '\0') { free_extension_info (info); return; } manager->priv->data = g_list_prepend (manager->priv->data, info); g_signal_emit (manager, signals[ADDED], 0, info); } static char * path_to_identifier (const char *path) { char *identifier, *dot; identifier = g_path_get_basename (path); dot = strstr (identifier, DOT_INI); if (!dot) { dot = strstr (identifier, ".xml"); } g_return_val_if_fail (dot != NULL, NULL); *dot = '\0'; return identifier; } static void ephy_extensions_manager_load_file (EphyExtensionsManager *manager, const char *path) { char *identifier; char *contents; GError *err = NULL; g_file_get_contents (path, &contents, NULL, &err); if (err != NULL) { g_warning ("Could not read file at '%s': '%s'", path, err->message); g_error_free (err); return; } identifier = path_to_identifier (path); g_return_if_fail (identifier != NULL); if (g_str_has_suffix (path, DOT_INI)) { ephy_extensions_manager_load_ini_string (manager, identifier, contents); } else if (g_str_has_suffix (path, ".xml")) { ephy_extensions_manager_load_xml_string (manager, identifier, contents); } g_free (identifier); g_free (contents); } static int find_loader (const LoaderInfo *info, const char *type) { return strcmp (info->type, type); } static char * sanitise_type (const char *string) { char *str, *p; str = g_strdup (string); for (p = str; *p != '\0'; p++) { if (!g_ascii_isalpha (*p)) *p = '-'; } return str; } static EphyLoader * get_loader_for_type (EphyExtensionsManager *manager, const char *type) { LoaderInfo *info; GList *l; GData *attr = NULL; char *path, *name, *stype; EphyLoader *shlib_loader; GObject *loader; LOG ("Looking for loader for type '%s'", type); l = g_list_find_custom (manager->priv->factories, type, (GCompareFunc) find_loader); if (l != NULL) { info = (LoaderInfo *) l->data; return g_object_ref (info->loader); } if (strcmp (type, "shlib") == 0) { info = g_new (LoaderInfo, 1); info->type = g_strdup (type); info->loader = g_object_new (EPHY_TYPE_SHLIB_LOADER, NULL); manager->priv->factories = g_list_append (manager->priv->factories, info); return g_object_ref (info->loader); } if (strcmp (type, "python") == 0) { #ifdef ENABLE_PYTHON info = g_new (LoaderInfo, 1); info->type = g_strdup (type); info->loader = g_object_new (EPHY_TYPE_PYTHON_LOADER, NULL); manager->priv->factories = g_list_append (manager->priv->factories, info); return g_object_ref (info->loader); #else return NULL; #endif } stype = sanitise_type (type); name = g_strconcat ("lib", stype, "loader.", G_MODULE_SUFFIX, NULL); path = g_build_filename (LOADER_DIR, name, NULL); g_datalist_init (&attr); g_datalist_set_data (&attr, "library", path); shlib_loader = get_loader_for_type (manager, "shlib"); g_return_val_if_fail (shlib_loader != NULL, NULL); loader = ephy_loader_get_object (shlib_loader, &attr); g_datalist_clear (&attr); g_free (stype); g_free (name); g_free (path); if (EPHY_IS_LOADER (loader)) { info = g_new (LoaderInfo, 1); info->type = g_strdup (type); info->loader = EPHY_LOADER (loader); manager->priv->factories = g_list_append (manager->priv->factories, info); return g_object_ref (info->loader); } g_return_val_if_reached (NULL); return NULL; } static void attach_window (EphyWindow *window, EphyExtension *extension) { GList *tabs, *l; ephy_extension_attach_window (extension, window); tabs = ephy_window_get_tabs (window); for (l = tabs; l; l = l->next) { ephy_extension_attach_tab (extension, window, EPHY_TAB (l->data)); } g_list_free (tabs); } static void load_extension (EphyExtensionsManager *manager, ExtensionInfo *info) { EphyLoader *loader; g_return_if_fail (info->extension == NULL); LOG ("Loading extension '%s'", info->info.identifier); /* don't try again */ if (info->load_failed) return; /* get a loader */ loader = get_loader_for_type (manager, info->loader_type); if (loader == NULL) { g_message ("No loader found for extension '%s' of type '%s'\n", info->info.identifier, info->loader_type); return; } info->loader = loader; info->extension = ephy_loader_get_object (loader, &info->loader_attributes); /* attach if the extension implements EphyExtensionIface */ if (EPHY_IS_EXTENSION (info->extension)) { manager->priv->extensions = g_list_prepend (manager->priv->extensions, g_object_ref (info->extension)); g_list_foreach (manager->priv->windows, (GFunc) attach_window, info->extension); } if (info->extension != NULL) { info->info.active = TRUE; g_signal_emit (manager, signals[CHANGED], 0, info); } else { info->info.active = FALSE; info->load_failed = TRUE; } } static void detach_window (EphyWindow *window, EphyExtension *extension) { GList *tabs, *l; tabs = ephy_window_get_tabs (window); for (l = tabs; l; l = l->next) { ephy_extension_detach_tab (extension, window, EPHY_TAB (l->data)); } g_list_free (tabs); ephy_extension_detach_window (extension, window); } static void unload_extension (EphyExtensionsManager *manager, ExtensionInfo *info) { g_return_if_fail (info->loader != NULL); g_return_if_fail (info->extension != NULL || info->load_failed); LOG ("Unloading extension '%s'", info->info.identifier); if (info->load_failed) return; /* detach if the extension implements EphyExtensionIface */ if (EPHY_IS_EXTENSION (info->extension)) { g_list_foreach (manager->priv->windows, (GFunc) detach_window, info->extension); manager->priv->extensions = g_list_remove (manager->priv->extensions, info->extension); /* we own two refs to the extension, the one we added when * we added it to the priv->extensions list, and the one returned * from get_object. Release object, and queue a unref, since if the * extension has its own functions queued in the idle loop, the * functions must exist in memory before being called. */ ephy_object_idle_unref (info->extension); } ephy_loader_release_object (info->loader, G_OBJECT (info->extension)); info->info.active = FALSE; info->extension = NULL; g_signal_emit (manager, signals[CHANGED], 0, info); } static void sync_loaded_extensions (EphyExtensionsManager *manager) { GConfClient *client; GConfValue *value; GSList *active_extensions = NULL; GList *l; gboolean active; ExtensionInfo *info; LOG ("Synching changed list of active extensions"); client = gconf_client_get_default (); g_return_if_fail (client != NULL); value = gconf_client_get (client, CONF_LOADED_EXTENSIONS, NULL); /* make sure the extensions-manager-ui is loaded */ if (value == NULL || value->type != GCONF_VALUE_LIST || gconf_value_get_list_type (value) != GCONF_VALUE_STRING) { active_extensions = g_slist_prepend (active_extensions, g_strdup ("extensions-manager-ui")); eel_gconf_set_string_list (CONF_LOADED_EXTENSIONS, active_extensions); } else { active_extensions = eel_gconf_get_string_list (CONF_LOADED_EXTENSIONS); } for (l = manager->priv->data; l != NULL; l = l->next) { info = (ExtensionInfo *) l->data; active = (g_slist_find_custom (active_extensions, info->info.identifier, (GCompareFunc) strcmp) != NULL); LOG ("Extension '%s' is %sactive and %sloaded", info->info.identifier, active ? "" : "not ", info->info.active ? "" : "not "); if (active != info->info.active) { if (active) { load_extension (manager, info); } else { unload_extension (manager, info); } } } g_slist_foreach (active_extensions, (GFunc) g_free, NULL); g_slist_free (active_extensions); if (value != NULL) { gconf_value_free (value); } g_object_unref (client); } static void ephy_extensions_manager_unload_file (EphyExtensionsManager *manager, const char *path) { GList *l; ExtensionInfo *info; char *identifier; identifier = path_to_identifier (path); l = g_list_find_custom (manager->priv->data, identifier, (GCompareFunc) find_extension_info); if (l != NULL) { info = (ExtensionInfo *) l->data; manager->priv->data = g_list_remove (manager->priv->data, info); if (info->info.active == TRUE) { unload_extension (manager, info); } g_signal_emit (manager, signals[REMOVED], 0, info); free_extension_info (info); } g_free (identifier); } static void load_file_from_monitor (EphyExtensionsManager *manager, const char *path) { /* When a file is installed, it sometimes gets CREATED empty and then * gets its contents filled later (for a CHANGED signal). Theoretically * I suppose we could get a CHANGED signal when the file is half-full, * but I doubt that'll happen much (the files are <1000 bytes). We * don't want warnings all over the place, so we return from this * function if the file is empty. (We're assuming that if a file is * empty it'll be filled soon and this function will be called again.) * * Oh, and we return if the extension is already loaded, too. */ char *identifier; char *contents; gsize len; GError *err = NULL; g_file_get_contents (path, &contents, &len, &err); if (err != NULL) { g_warning ("Could not read file at '%s': '%s'", path, err->message); g_error_free (err); return; } if (len == 0) { g_free (contents); return; } identifier = path_to_identifier (path); g_return_if_fail (identifier != NULL); if (g_list_find_custom (manager->priv->data, identifier, (GCompareFunc) find_extension_info) != NULL) { g_free (identifier); g_free (contents); return; } if (g_str_has_suffix (path, DOT_INI)) { ephy_extensions_manager_load_ini_string (manager, identifier, contents); } else if (g_str_has_suffix (path, ".xml")) { ephy_extensions_manager_load_xml_string (manager, identifier, contents); } g_free (identifier); g_free (contents); sync_loaded_extensions (manager); } static void dir_changed_cb (GnomeVFSMonitorHandle *handle, const char *monitor_uri, const char *info_uri, GnomeVFSMonitorEventType event_type, EphyExtensionsManager *manager) { char *path; /* * We only deal with XML and INI files: * Add them to the manager when created, remove them when deleted. */ if (g_str_has_suffix (info_uri, DOT_INI) == FALSE && g_str_has_suffix (info_uri, ".xml") == FALSE) return; path = gnome_vfs_get_local_path_from_uri (info_uri); switch (event_type) { case GNOME_VFS_MONITOR_EVENT_CREATED: case GNOME_VFS_MONITOR_EVENT_CHANGED: load_file_from_monitor (manager, path); break; case GNOME_VFS_MONITOR_EVENT_DELETED: ephy_extensions_manager_unload_file (manager, path); break; default: break; } g_free (path); } static void ephy_extensions_manager_load_dir (EphyExtensionsManager *manager, const char *path) { DIR *d; struct dirent *e; char *file_path; char *file_uri; GnomeVFSMonitorHandle *monitor; GnomeVFSResult res; LOG ("Scanning directory '%s'", path); START_PROFILER ("Scanning directory") d = opendir (path); if (d == NULL) { return; } while ((e = readdir (d)) != NULL) { if (g_str_has_suffix (e->d_name, DOT_INI) || g_str_has_suffix (e->d_name, ".xml")) { file_path = g_build_filename (path, e->d_name, NULL); ephy_extensions_manager_load_file (manager, file_path); g_free (file_path); } } closedir (d); file_uri = gnome_vfs_get_uri_from_local_path (path); res = gnome_vfs_monitor_add (&monitor, path, GNOME_VFS_MONITOR_DIRECTORY, (GnomeVFSMonitorCallback) dir_changed_cb, manager); g_free (file_uri); if (res == GNOME_VFS_OK) { manager->priv->dir_monitors = g_list_prepend (manager->priv->dir_monitors, monitor); } STOP_PROFILER ("Scanning directory") } static void active_extensions_notifier (GConfClient *client, guint cnxn_id, GConfEntry *entry, EphyExtensionsManager *manager) { sync_loaded_extensions (manager); } static void ephy_extensions_manager_init (EphyExtensionsManager *manager) { manager->priv = EPHY_EXTENSIONS_MANAGER_GET_PRIVATE (manager); LOG ("EphyExtensionsManager initialising"); } void ephy_extensions_manager_startup (EphyExtensionsManager *manager) { char *path; g_return_if_fail (EPHY_IS_EXTENSIONS_MANAGER (manager)); LOG ("EphyExtensionsManager startup"); /* load the extensions descriptions */ path = g_build_filename (ephy_dot_dir (), "extensions", NULL); ephy_extensions_manager_load_dir (manager, path); g_free (path); ephy_extensions_manager_load_dir (manager, EXTENSIONS_DIR); active_extensions_notifier (NULL, 0, NULL, manager); manager->priv->active_extensions_notifier_id = eel_gconf_notification_add (CONF_LOADED_EXTENSIONS, (GConfClientNotifyFunc) active_extensions_notifier, manager); } static void ephy_extensions_manager_finalize (GObject *object) { EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (object); EphyExtensionsManagerPrivate *priv = manager->priv; LOG ("EphyExtensionsManager finalising"); eel_gconf_notification_remove (manager->priv->active_extensions_notifier_id); g_list_foreach (priv->dir_monitors, (GFunc) gnome_vfs_monitor_cancel, NULL); g_list_free (priv->dir_monitors); g_list_foreach (priv->extensions, (GFunc) g_object_unref, NULL); g_list_free (priv->extensions); /* FIXME release loaded loaders */ g_list_foreach (priv->factories, (GFunc) free_loader_info, NULL); g_list_free (priv->factories); g_list_foreach (priv->data, (GFunc) free_extension_info, NULL); g_list_free (priv->data); g_list_free (priv->windows); parent_class->finalize (object); } static void attach_extension_to_window (EphyExtension *extension, EphyWindow *window) { attach_window (window, extension); } static void impl_attach_window (EphyExtension *extension, EphyWindow *window) { EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (extension); LOG ("Attach window %p", window); g_list_foreach (manager->priv->extensions, (GFunc) attach_extension_to_window, window); manager->priv->windows = g_list_prepend (manager->priv->windows, window); } static void impl_detach_window (EphyExtension *extension, EphyWindow *window) { EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (extension); GList *tabs, *l; LOG ("Detach window %p", window); manager->priv->windows = g_list_remove (manager->priv->windows, window); g_object_ref (window); /* Detach tabs (uses impl_detach_tab) */ tabs = ephy_window_get_tabs (window); for (l = tabs; l; l = l->next) { ephy_extension_detach_tab (extension, window, EPHY_TAB (l->data)); } g_list_free (tabs); /* Then detach the window */ g_list_foreach (manager->priv->extensions, (GFunc) ephy_extension_detach_window, window); g_object_unref (window); } static void impl_attach_tab (EphyExtension *extension, EphyWindow *window, EphyTab *tab) { EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (extension); GList *l; LOG ("Attach window %p tab %p", window, tab); for (l = manager->priv->extensions; l; l = l->next) { ephy_extension_attach_tab (EPHY_EXTENSION (l->data), window, tab); } } static void impl_detach_tab (EphyExtension *extension, EphyWindow *window, EphyTab *tab) { EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (extension); GList *l; LOG ("Detach window %p tab %p", window, tab); g_object_ref (window); g_object_ref (tab); for (l = manager->priv->extensions; l; l = l->next) { ephy_extension_detach_tab (EPHY_EXTENSION (l->data), window, tab); } g_object_unref (tab); g_object_unref (window); } static void ephy_extensions_manager_iface_init (EphyExtensionIface *iface) { iface->attach_window = impl_attach_window; iface->detach_window = impl_detach_window; iface->attach_tab = impl_attach_tab; iface->detach_tab = impl_detach_tab; } static void ephy_extensions_manager_class_init (EphyExtensionsManagerClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); parent_class = (GObjectClass *) g_type_class_peek_parent (class); object_class->finalize = ephy_extensions_manager_finalize; signals[CHANGED] = g_signal_new ("changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EphyExtensionsManagerClass, changed), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[ADDED] = g_signal_new ("added", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EphyExtensionsManagerClass, added), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[REMOVED] = g_signal_new ("removed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (EphyExtensionsManagerClass, removed), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); g_type_class_add_private (object_class, sizeof (EphyExtensionsManagerPrivate)); languages = g_get_language_names (); g_assert (languages != NULL); }