Initial import of ephy (rev# 7126) from svn
[ephy-soc.git] / src / ephy-extensions-manager.c
blob1046986d391b4ea57773ba30f69af945ce47cbb4
1 /*
2 * Copyright © 2003 Marco Pesenti Gritti
3 * Copyright © 2003, 2004 Christian Persch
4 * Copyright © 2004 Adam Hooper
5 * Copyright © 2005 Crispin Flowerday
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * $Id: ephy-extensions-manager.c 6952 2007-03-11 19:42:02Z chpe $
24 #include "config.h"
26 #include "ephy-extensions-manager.h"
28 #include "ephy-loader.h"
29 #include "ephy-shlib-loader.h"
31 #include "ephy-node-db.h"
32 #include "ephy-shell.h"
33 #include "eel-gconf-extensions.h"
34 #include "ephy-file-helpers.h"
35 #include "ephy-object-helpers.h"
36 #include "ephy-debug.h"
38 #include "ephy-glib-compat.h"
40 #include <libxml/tree.h>
41 #include <libxml/xmlreader.h>
42 #include <libxml/globals.h>
43 #include <libxml/tree.h>
44 #include <libxml/xmlwriter.h>
45 #include <libxslt/xslt.h>
46 #include <libxslt/transform.h>
47 #include <libxslt/xsltutils.h>
49 #include <libgnomevfs/gnome-vfs-ops.h>
50 #include <libgnomevfs/gnome-vfs-utils.h>
52 #include <gconf/gconf-client.h>
54 #include <gmodule.h>
55 #include <dirent.h>
56 #include <string.h>
58 #ifdef ENABLE_PYTHON
59 #include "ephy-python-extension.h"
60 #include "ephy-python-loader.h"
61 #endif
63 #define CONF_LOADED_EXTENSIONS "/apps/epiphany/general/active_extensions"
64 #define EE_GROUP "Epiphany Extension"
65 #define DOT_INI ".ephy-extension"
66 #define RELOAD_DELAY 333 /* ms */
67 #define RELOAD_SYNC_DELAY 1 /* seconds */
69 #define ENABLE_LEGACY_FORMAT
71 #define EPHY_EXTENSIONS_MANAGER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_EXTENSIONS_MANAGER, EphyExtensionsManagerPrivate))
73 struct _EphyExtensionsManagerPrivate
75 gboolean initialised;
77 GList *data;
78 GList *factories;
79 GList *extensions;
80 GList *dir_monitors;
81 GList *windows;
82 guint active_extensions_notifier_id;
83 guint sync_timeout_id;
84 GHashTable *reload_hash;
86 #ifdef ENABLE_LEGACY_FORMAT
87 xsltStylesheetPtr xml2ini_xsl;
88 #endif
91 typedef struct
93 EphyExtensionInfo info;
94 gboolean load_failed;
96 char *loader_type;
98 EphyLoader *loader; /* NULL if never loaded */
99 GObject *extension; /* NULL if unloaded */
101 #ifdef ENABLE_LEGACY_FORMAT
102 guint is_legacy_format : 1;
103 #endif
104 } ExtensionInfo;
106 typedef struct
108 char *type;
109 EphyLoader *loader;
110 } LoaderInfo;
112 typedef enum
114 FORMAT_UNKNOWN,
115 FORMAT_INI
116 #ifdef ENABLE_LEGACY_FORMAT
117 , FORMAT_XML
118 #endif
119 } ExtensionFormat;
121 enum
123 CHANGED,
124 ADDED,
125 REMOVED,
126 LAST_SIGNAL
129 static guint signals[LAST_SIGNAL] = { 0 };
131 static GObjectClass *parent_class = NULL;
133 static void ephy_extensions_manager_class_init (EphyExtensionsManagerClass *klass);
134 static void ephy_extensions_manager_iface_init (EphyExtensionIface *iface);
135 static void ephy_extensions_manager_init (EphyExtensionsManager *manager);
137 GType
138 ephy_extensions_manager_get_type (void)
140 static GType type = 0;
142 if (G_UNLIKELY (type == 0))
144 const GTypeInfo our_info =
146 sizeof (EphyExtensionsManagerClass),
147 NULL, /* base_init */
148 NULL, /* base_finalize */
149 (GClassInitFunc) ephy_extensions_manager_class_init,
150 NULL,
151 NULL, /* class_data */
152 sizeof (EphyExtensionsManager),
153 0, /* n_preallocs */
154 (GInstanceInitFunc) ephy_extensions_manager_init
157 const GInterfaceInfo extension_info =
159 (GInterfaceInitFunc) ephy_extensions_manager_iface_init,
160 NULL,
161 NULL
164 type = g_type_register_static (G_TYPE_OBJECT,
165 "EphyExtensionsManager",
166 &our_info, 0);
168 g_type_add_interface_static (type,
169 EPHY_TYPE_EXTENSION,
170 &extension_info);
173 return type;
177 * ephy_extensions_manager_load:
178 * @manager: an #EphyExtensionsManager
179 * @name: identifier of the extension to load
181 * Loads the @name extension.
183 void
184 ephy_extensions_manager_load (EphyExtensionsManager *manager,
185 const char *identifier)
187 GSList *gconf_exts;
189 g_return_if_fail (EPHY_IS_EXTENSIONS_MANAGER (manager));
190 g_return_if_fail (identifier != NULL);
192 LOG ("Adding '%s' to extensions", identifier);
194 gconf_exts = eel_gconf_get_string_list (CONF_LOADED_EXTENSIONS);
196 if (!g_slist_find_custom (gconf_exts, identifier, (GCompareFunc) strcmp))
198 gconf_exts = g_slist_prepend (gconf_exts, g_strdup (identifier));
200 eel_gconf_set_string_list (CONF_LOADED_EXTENSIONS, gconf_exts);
203 g_slist_foreach (gconf_exts, (GFunc) g_free, NULL);
204 g_slist_free (gconf_exts);
208 * ephy_extensions_manager_unload:
209 * @manager: an #EphyExtensionsManager
210 * @name: filename of extension to unload, minus "lib" and "extension.so"
212 * Unloads the extension specified by @name.
214 * The extension with the same filename can afterwards be reloaded. However,
215 * if any GTypes within the extension have changed parent types, Epiphany must
216 * be restarted.
218 void
219 ephy_extensions_manager_unload (EphyExtensionsManager *manager,
220 const char *identifier)
222 GSList *gconf_exts;
223 GSList *l;
225 g_return_if_fail (EPHY_IS_EXTENSIONS_MANAGER (manager));
226 g_return_if_fail (identifier != NULL);
228 LOG ("Removing '%s' from extensions", identifier);
230 gconf_exts = eel_gconf_get_string_list (CONF_LOADED_EXTENSIONS);
232 l = g_slist_find_custom (gconf_exts, identifier, (GCompareFunc) strcmp);
234 if (l != NULL)
236 gconf_exts = g_slist_remove_link (gconf_exts, l);
237 g_free (l->data);
238 g_slist_free_1 (l);
240 eel_gconf_set_string_list (CONF_LOADED_EXTENSIONS, gconf_exts);
243 g_slist_foreach (gconf_exts, (GFunc) g_free, NULL);
244 g_slist_free (gconf_exts);
248 * ephy_extensions_manager_register:
249 * @manager: an #EphyExtensionsManager
250 * @object: an Extension
252 * Registers @object with the extensions manager. @object must implement the
253 * #EphyExtension interface.
255 void
256 ephy_extensions_manager_register (EphyExtensionsManager *manager,
257 GObject *object)
259 g_return_if_fail (EPHY_IS_EXTENSIONS_MANAGER (manager));
260 g_return_if_fail (EPHY_IS_EXTENSION (object));
262 manager->priv->extensions = g_list_prepend (manager->priv->extensions,
263 g_object_ref (object));
268 * ephy_extensions_manager_get_extensions:
269 * @manager: an #EphyExtensionsManager
271 * Returns the list of known extensions.
273 * Returns: a list of #EphyExtensionInfo
275 GList *
276 ephy_extensions_manager_get_extensions (EphyExtensionsManager *manager)
278 return g_list_copy (manager->priv->data);
281 static void
282 free_extension_info (ExtensionInfo *info)
284 EphyExtensionInfo *einfo = (EphyExtensionInfo *) info;
286 g_free (einfo->identifier);
287 g_key_file_free (einfo->keyfile);
288 g_free (info->loader_type);
290 if (info->extension != NULL)
292 g_return_if_fail (info->loader != NULL);
294 ephy_loader_release_object (info->loader, info->extension);
296 if (info->loader != NULL)
298 g_object_unref (info->loader);
301 g_free (info);
304 static void
305 free_loader_info (LoaderInfo *info)
307 g_free (info->type);
308 g_object_unref (info->loader);
309 g_free (info);
312 static int
313 find_extension_info (const ExtensionInfo *info,
314 const char *identifier)
316 return strcmp (info->info.identifier, identifier);
319 static ExtensionInfo *
320 ephy_extensions_manager_parse_keyfile (EphyExtensionsManager *manager,
321 GKeyFile *key_file,
322 const char *identifier)
324 ExtensionInfo *info;
325 EphyExtensionInfo *einfo;
326 char *start_group;
328 LOG ("Parsing INI description file for '%s'", identifier);
330 start_group = g_key_file_get_start_group (key_file);
331 if (start_group == NULL ||
332 strcmp (start_group, EE_GROUP) != 0 ||
333 !g_key_file_has_group (key_file, "Loader"))
335 g_warning ("Invalid extension description file for '%s'; "
336 "missing 'Epiphany Extension' or 'Loader' group",
337 identifier);
339 g_key_file_free (key_file);
340 g_free (start_group);
341 return NULL;
343 g_free (start_group);
345 if (!g_key_file_has_key (key_file, EE_GROUP, "Name", NULL) ||
346 !g_key_file_has_key (key_file, EE_GROUP, "Description", NULL))
348 g_warning ("Invalid extension description file for '%s'; "
349 "missing 'Name' or 'Description' keys.",
350 identifier);
352 g_key_file_free (key_file);
353 return NULL;
356 info = g_new0 (ExtensionInfo, 1);
357 einfo = (EphyExtensionInfo *) info;
358 einfo->identifier = g_strdup (identifier);
359 einfo->keyfile = key_file;
361 info->loader_type = g_key_file_get_string (key_file, "Loader", "Type", NULL);
363 /* sanity check */
364 if (info->loader_type == NULL || info->loader_type[0] == '\0')
366 free_extension_info (info);
367 return NULL;
370 manager->priv->data = g_list_prepend (manager->priv->data, info);
372 g_signal_emit (manager, signals[ADDED], 0, info);
374 return info;
377 static void
378 ephy_extensions_manager_load_ini_file (EphyExtensionsManager *manager,
379 const char *identifier,
380 const char *path)
382 GKeyFile *keyfile;
383 GError *err = NULL;
385 keyfile = g_key_file_new ();
386 if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, &err))
388 g_warning ("Could load key file for '%s': '%s'",
389 identifier, err->message);
390 g_error_free (err);
391 g_key_file_free (keyfile);
392 return;
395 ephy_extensions_manager_parse_keyfile (manager, keyfile, identifier);
398 #ifdef ENABLE_LEGACY_FORMAT
400 static void
401 ephy_extensions_manager_load_xml_file (EphyExtensionsManager *manager,
402 const char *identifier,
403 const char *path)
405 EphyExtensionsManagerPrivate *priv = manager->priv;
406 ExtensionInfo *info;
407 xmlDocPtr doc, res;
408 const xmlChar *xsl_file;
409 xmlChar *output = NULL;
410 int outlen = -1, ret = -1;
412 START_PROFILER ("Transforming .xml -> " DOT_INI)
414 doc = xmlParseFile (path);
415 if (!doc) goto out;
417 if (priv->xml2ini_xsl == NULL)
419 xsl_file = (const xmlChar *) ephy_file ("ephy-xml2ini.xsl");
420 if (!xsl_file) return;
422 priv->xml2ini_xsl = xsltParseStylesheetFile (xsl_file);
423 if (priv->xml2ini_xsl == NULL)
425 g_warning ("Couldn't parse the XSL to transform .xml extension descriptions!\n");
426 goto out;
430 res = xsltApplyStylesheet (priv->xml2ini_xsl, doc, NULL);
431 if (!res) goto out;
433 ret = xsltSaveResultToString (&output, &outlen, res, priv->xml2ini_xsl);
435 if (ret >= 0 && output != NULL && outlen > -1)
437 GKeyFile *keyfile;
438 GError *err = NULL;
440 keyfile = g_key_file_new ();
441 if (!g_key_file_load_from_data (keyfile, (char *) output, outlen,
442 G_KEY_FILE_NONE, &err))
444 g_warning ("Could load converted key file for '%s': '%s'",
445 identifier, err->message);
446 g_error_free (err);
447 g_key_file_free (keyfile);
448 goto out;
451 info = ephy_extensions_manager_parse_keyfile (manager, keyfile, identifier);
452 if (info != NULL)
454 info->is_legacy_format = TRUE;
458 xmlFreeDoc (res);
459 xmlFreeDoc (doc);
461 out:
462 xmlFree (output);
464 STOP_PROFILER ("Transforming .xml -> " DOT_INI)
467 #endif /* ENABLE_LEGACY_FORMAT */
469 static char *
470 path_to_identifier (const char *path)
472 char *identifier, *dot;
474 identifier = g_path_get_basename (path);
475 dot = strstr (identifier, DOT_INI);
477 #ifdef ENABLE_LEGACY_FORMAT
478 if (!dot)
480 dot = strstr (identifier, ".xml");
482 #endif
484 g_return_val_if_fail (dot != NULL, NULL);
486 *dot = '\0';
488 return identifier;
491 static ExtensionFormat
492 format_from_path (const char *path)
494 ExtensionFormat format = FORMAT_UNKNOWN;
496 if (g_str_has_suffix (path, DOT_INI))
498 format = FORMAT_INI;
500 #ifdef ENABLE_LEGACY_FORMAT
501 else if (g_str_has_suffix (path, ".xml"))
503 format = FORMAT_XML;
505 #endif
507 return format;
510 static void
511 ephy_extensions_manager_load_file (EphyExtensionsManager *manager,
512 const char *path)
514 GList *element;
515 char *identifier;
516 ExtensionFormat format;
518 identifier = path_to_identifier (path);
519 g_return_if_fail (identifier != NULL);
520 if (identifier == NULL) return;
522 format = format_from_path (path);
523 g_return_if_fail (format != FORMAT_UNKNOWN);
525 element = g_list_find_custom (manager->priv->data, identifier,
526 (GCompareFunc) find_extension_info);
527 if (element != NULL)
529 #ifdef ENABLE_LEGACY_FORMAT
530 ExtensionInfo *info = (ExtensionInfo *) element->data;
532 /* If this is the legacy format and we already have the info
533 * read for this type from a non-legacy format file, don't
534 * warn.
536 if (format == FORMAT_XML && !info->is_legacy_format)
537 #endif
539 g_warning ("Extension description for '%s' already read!",
540 identifier);
543 g_free (identifier);
544 return;
547 if (format == FORMAT_INI)
549 ephy_extensions_manager_load_ini_file (manager, identifier,
550 path);
552 #ifdef ENABLE_LEGACY_FORMAT
553 else if (format == FORMAT_XML)
555 ephy_extensions_manager_load_xml_file (manager, identifier,
556 path);
558 #endif
560 g_free (identifier);
564 static int
565 find_loader (const LoaderInfo *info,
566 const char *type)
568 return strcmp (info->type, type);
571 static char *
572 sanitise_type (const char *string)
574 char *str, *p;
576 str = g_strdup (string);
577 for (p = str; *p != '\0'; p++)
579 if (!g_ascii_isalpha (*p)) *p = '-';
582 return str;
585 static EphyLoader *
586 get_loader_for_type (EphyExtensionsManager *manager,
587 const char *type)
589 LoaderInfo *info;
590 GList *l;
591 char *path, *name, *stype, *data;
592 GKeyFile *keyfile;
593 EphyLoader *shlib_loader;
594 GObject *loader;
596 LOG ("Looking for loader for type '%s'", type);
598 l = g_list_find_custom (manager->priv->factories, type,
599 (GCompareFunc) find_loader);
600 if (l != NULL)
602 info = (LoaderInfo *) l->data;
603 return g_object_ref (info->loader);
606 if (strcmp (type, "shlib") == 0)
608 info = g_new (LoaderInfo, 1);
609 info->type = g_strdup (type);
610 info->loader = g_object_new (EPHY_TYPE_SHLIB_LOADER, NULL);
612 manager->priv->factories =
613 g_list_append (manager->priv->factories, info);
615 return g_object_ref (info->loader);
617 if (strcmp (type, "python") == 0)
619 #ifdef ENABLE_PYTHON
620 info = g_new (LoaderInfo, 1);
621 info->type = g_strdup (type);
622 info->loader = g_object_new (EPHY_TYPE_PYTHON_LOADER, NULL);
624 manager->priv->factories =
625 g_list_append (manager->priv->factories, info);
627 return g_object_ref (info->loader);
628 #else
629 return NULL;
630 #endif
633 shlib_loader = get_loader_for_type (manager, "shlib");
634 g_return_val_if_fail (shlib_loader != NULL, NULL);
636 stype = sanitise_type (type);
637 name = g_strconcat ("lib", stype, "loader.", G_MODULE_SUFFIX, NULL);
638 path = g_build_filename (LOADER_DIR, name, NULL);
639 data = g_strconcat ("[Loader]\nType=shlib\nLibrary=", path, "\n", NULL);
640 g_free (stype);
641 g_free (name);
642 g_free (path);
644 keyfile = g_key_file_new ();
645 if (!g_key_file_load_from_data (keyfile, data, strlen (data), 0, NULL))
647 g_free (data);
648 return NULL;
651 loader = ephy_loader_get_object (shlib_loader, keyfile);
652 g_key_file_free (keyfile);
654 if (EPHY_IS_LOADER (loader))
656 info = g_new (LoaderInfo, 1);
657 info->type = g_strdup (type);
658 info->loader = EPHY_LOADER (loader);
660 manager->priv->factories =
661 g_list_append (manager->priv->factories, info);
663 return g_object_ref (info->loader);
666 g_return_val_if_reached (NULL);
668 return NULL;
671 static void
672 attach_window (EphyWindow *window,
673 EphyExtension *extension)
675 GList *tabs, *l;
677 ephy_extension_attach_window (extension, window);
679 tabs = ephy_window_get_tabs (window);
680 for (l = tabs; l; l = l->next)
682 ephy_extension_attach_tab (extension, window,
683 EPHY_TAB (l->data));
685 g_list_free (tabs);
688 static void
689 load_extension (EphyExtensionsManager *manager,
690 ExtensionInfo *info)
692 EphyLoader *loader;
694 g_return_if_fail (info->extension == NULL);
696 LOG ("Loading extension '%s'", info->info.identifier);
698 /* don't try again */
699 if (info->load_failed) return;
701 /* get a loader */
702 loader = get_loader_for_type (manager, info->loader_type);
703 if (loader == NULL)
705 g_message ("No loader found for extension '%s' of type '%s'\n",
706 info->info.identifier, info->loader_type);
707 return;
710 info->loader = loader;
712 info->extension = ephy_loader_get_object (loader, info->info.keyfile);
714 /* attach if the extension implements EphyExtensionIface */
715 if (EPHY_IS_EXTENSION (info->extension))
717 manager->priv->extensions =
718 g_list_prepend (manager->priv->extensions,
719 g_object_ref (info->extension));
721 g_list_foreach (manager->priv->windows, (GFunc) attach_window,
722 info->extension);
725 if (info->extension != NULL)
727 info->info.active = TRUE;
729 else
731 info->info.active = FALSE;
732 info->load_failed = TRUE;
736 static void
737 detach_window (EphyWindow *window,
738 EphyExtension *extension)
740 GList *tabs, *l;
742 tabs = ephy_window_get_tabs (window);
743 for (l = tabs; l; l = l->next)
745 ephy_extension_detach_tab (extension, window,
746 EPHY_TAB (l->data));
748 g_list_free (tabs);
750 ephy_extension_detach_window (extension, window);
753 static void
754 unload_extension (EphyExtensionsManager *manager,
755 ExtensionInfo *info)
757 g_return_if_fail (info->loader != NULL);
758 g_return_if_fail (info->extension != NULL || info->load_failed);
760 LOG ("Unloading extension '%s'", info->info.identifier);
762 if (info->load_failed) return;
764 /* detach if the extension implements EphyExtensionIface */
765 if (EPHY_IS_EXTENSION (info->extension))
767 g_list_foreach (manager->priv->windows, (GFunc) detach_window,
768 info->extension);
770 manager->priv->extensions =
771 g_list_remove (manager->priv->extensions, info->extension);
773 /* we own two refs to the extension, the one we added when
774 * we added it to the priv->extensions list, and the one returned
775 * from get_object. Release object, and queue a unref, since if the
776 * extension has its own functions queued in the idle loop, the
777 * functions must exist in memory before being called.
779 ephy_object_idle_unref (info->extension);
782 ephy_loader_release_object (info->loader, G_OBJECT (info->extension));
784 info->info.active = FALSE;
785 info->extension = NULL;
788 static void
789 sync_loaded_extensions (EphyExtensionsManager *manager)
791 GConfClient *client;
792 GConfValue *value;
793 GSList *active_extensions = NULL;
794 GList *l;
795 gboolean active;
796 ExtensionInfo *info;
798 LOG ("Synching changed list of active extensions");
800 client = gconf_client_get_default ();
801 g_return_if_fail (client != NULL);
803 value = gconf_client_get (client, CONF_LOADED_EXTENSIONS, NULL);
805 /* make sure the extensions-manager-ui is loaded */
806 if (value == NULL ||
807 value->type != GCONF_VALUE_LIST ||
808 gconf_value_get_list_type (value) != GCONF_VALUE_STRING)
810 active_extensions = g_slist_prepend (active_extensions,
811 g_strdup ("extensions-manager-ui"));
812 eel_gconf_set_string_list (CONF_LOADED_EXTENSIONS, active_extensions);
814 else
816 active_extensions = eel_gconf_get_string_list (CONF_LOADED_EXTENSIONS);
819 for (l = manager->priv->data; l != NULL; l = l->next)
821 gboolean changed;
823 info = (ExtensionInfo *) l->data;
825 active = (g_slist_find_custom (active_extensions,
826 info->info.identifier,
827 (GCompareFunc) strcmp) != NULL);
829 LOG ("Extension '%s' is %sactive and %sloaded",
830 info->info.identifier,
831 active ? "" : "not ",
832 info->info.active ? "" : "not ");
834 changed = ( info->info.enabled != active );
836 info->info.enabled = active;
838 if (active != info->info.active)
840 if (active)
842 load_extension (manager, info);
844 else
846 unload_extension (manager, info);
849 if (active == info->info.active)
851 changed = TRUE;
855 if (changed)
857 g_signal_emit (manager, signals[CHANGED], 0, info);
861 g_slist_foreach (active_extensions, (GFunc) g_free, NULL);
862 g_slist_free (active_extensions);
864 if (value != NULL)
866 gconf_value_free (value);
868 g_object_unref (client);
871 static void
872 ephy_extensions_manager_unload_file (EphyExtensionsManager *manager,
873 const char *path)
875 GList *l;
876 ExtensionInfo *info;
877 char *identifier;
879 identifier = path_to_identifier (path);
881 l = g_list_find_custom (manager->priv->data, identifier,
882 (GCompareFunc) find_extension_info);
884 if (l != NULL)
886 info = (ExtensionInfo *) l->data;
888 manager->priv->data = g_list_remove (manager->priv->data, info);
890 if (info->info.active == TRUE)
892 unload_extension (manager, info);
895 g_signal_emit (manager, signals[REMOVED], 0, info);
897 free_extension_info (info);
900 g_free (identifier);
903 static gboolean
904 reload_sync_cb (EphyExtensionsManager *manager)
906 EphyExtensionsManagerPrivate *priv = manager->priv;
908 if (priv->sync_timeout_id != 0)
910 g_source_remove (priv->sync_timeout_id);
911 priv->sync_timeout_id = 0;
914 sync_loaded_extensions (manager);
916 return FALSE;
919 static gboolean
920 reload_cb (gpointer *data)
922 EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (data[0]);
923 EphyExtensionsManagerPrivate *priv = manager->priv;
924 char *path = data[1];
926 LOG ("Reloading %s", path);
928 /* We still need path and don't want to remove the timeout
929 * which will be removed automatically when we return, so
930 * just use _steal instead of _remove.
932 g_hash_table_steal (priv->reload_hash, path);
934 ephy_extensions_manager_load_file (manager, path);
935 g_free (path);
937 /* Schedule a sync of active extensions */
938 /* FIXME: just look if we need to activate *this* extension? */
940 if (priv->sync_timeout_id != 0)
942 g_source_remove (priv->sync_timeout_id);
945 priv->sync_timeout_id = g_timeout_add_seconds (RELOAD_SYNC_DELAY,
946 (GSourceFunc) reload_sync_cb,
947 manager);
948 return FALSE;
951 static void
952 schedule_load_from_monitor (EphyExtensionsManager *manager,
953 const char *path)
955 EphyExtensionsManagerPrivate *priv = manager->priv;
956 char *identifier, *copy;
957 gpointer *data;
958 guint timeout_id;
960 /* When a file is installed, it sometimes gets CREATED empty and then
961 * gets its contents filled later (for a CHANGED signal). Theoretically
962 * I suppose we could get a CHANGED signal when the file is half-full,
963 * but I doubt that'll happen much (the files are <1000 bytes). We
964 * don't want warnings all over the place, so we just wait a bit before
965 * actually reloading the file. (We're assuming that if a file is
966 * empty it'll be filled soon and this function will be called again.)
968 * Oh, and we return if the extension is already loaded, too.
971 identifier = path_to_identifier (path);
972 g_return_if_fail (identifier != NULL);
973 if (identifier == NULL) return;
975 if (g_list_find_custom (manager->priv->data, identifier,
976 (GCompareFunc) find_extension_info) != NULL)
978 g_free (identifier);
979 return;
981 g_free (identifier);
983 g_return_if_fail (priv->reload_hash != NULL);
985 data = g_new (gpointer, 2);
986 data[0] = (gpointer) manager;
987 data[1] = copy = g_strdup (path);
988 timeout_id = g_timeout_add_full (G_PRIORITY_LOW, RELOAD_DELAY,
989 (GSourceFunc) reload_cb,
990 data, (GDestroyNotify) g_free);
991 g_hash_table_replace (priv->reload_hash, copy /* owns it */,
992 GUINT_TO_POINTER (timeout_id));
995 static void
996 dir_changed_cb (GnomeVFSMonitorHandle *handle,
997 const char *monitor_uri,
998 const char *info_uri,
999 GnomeVFSMonitorEventType event_type,
1000 EphyExtensionsManager *manager)
1002 char *path;
1005 * We only deal with XML and INI files:
1006 * Add them to the manager when created, remove them when deleted.
1008 if (format_from_path (info_uri) == FORMAT_UNKNOWN) return;
1010 path = gnome_vfs_get_local_path_from_uri (info_uri);
1012 switch (event_type)
1014 case GNOME_VFS_MONITOR_EVENT_CREATED:
1015 case GNOME_VFS_MONITOR_EVENT_CHANGED:
1016 schedule_load_from_monitor (manager, path);
1017 break;
1018 case GNOME_VFS_MONITOR_EVENT_DELETED:
1019 ephy_extensions_manager_unload_file (manager, path);
1020 break;
1021 default:
1022 break;
1025 g_free (path);
1028 static void
1029 ephy_extensions_manager_load_dir (EphyExtensionsManager *manager,
1030 const char *path)
1032 DIR *d;
1033 struct dirent *e;
1034 char *file_path;
1035 char *file_uri;
1036 GnomeVFSMonitorHandle *monitor;
1037 GnomeVFSResult res;
1039 LOG ("Scanning directory '%s'", path);
1041 START_PROFILER ("Scanning directory")
1043 d = opendir (path);
1044 if (d == NULL)
1046 return;
1048 while ((e = readdir (d)) != NULL)
1050 if (format_from_path (e->d_name) != FORMAT_UNKNOWN)
1052 file_path = g_build_filename (path, e->d_name, NULL);
1053 ephy_extensions_manager_load_file (manager, file_path);
1054 g_free (file_path);
1057 closedir (d);
1059 file_uri = gnome_vfs_get_uri_from_local_path (path);
1060 res = gnome_vfs_monitor_add (&monitor,
1061 path,
1062 GNOME_VFS_MONITOR_DIRECTORY,
1063 (GnomeVFSMonitorCallback) dir_changed_cb,
1064 manager);
1065 g_free (file_uri);
1067 if (res == GNOME_VFS_OK)
1069 manager->priv->dir_monitors = g_list_prepend
1070 (manager->priv->dir_monitors, monitor);
1073 STOP_PROFILER ("Scanning directory")
1076 static void
1077 active_extensions_notifier (GConfClient *client,
1078 guint cnxn_id,
1079 GConfEntry *entry,
1080 EphyExtensionsManager *manager)
1082 sync_loaded_extensions (manager);
1085 static void
1086 cancel_timeout (gpointer data)
1088 guint id = GPOINTER_TO_UINT (data);
1090 g_source_remove (id);
1093 static void
1094 ephy_extensions_manager_init (EphyExtensionsManager *manager)
1096 EphyExtensionsManagerPrivate *priv;
1098 priv = manager->priv = EPHY_EXTENSIONS_MANAGER_GET_PRIVATE (manager);
1100 priv->reload_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
1101 (GDestroyNotify) g_free,
1102 (GDestroyNotify) cancel_timeout);
1105 void
1106 ephy_extensions_manager_startup (EphyExtensionsManager *manager)
1108 char *path;
1110 g_return_if_fail (EPHY_IS_EXTENSIONS_MANAGER (manager));
1112 LOG ("EphyExtensionsManager startup");
1114 /* load the extensions descriptions */
1115 path = g_build_filename (ephy_dot_dir (), "extensions", NULL);
1116 ephy_extensions_manager_load_dir (manager, path);
1117 g_free (path);
1119 ephy_extensions_manager_load_dir (manager, EXTENSIONS_DIR);
1121 active_extensions_notifier (NULL, 0, NULL, manager);
1122 manager->priv->active_extensions_notifier_id =
1123 eel_gconf_notification_add
1124 (CONF_LOADED_EXTENSIONS,
1125 (GConfClientNotifyFunc) active_extensions_notifier,
1126 manager);
1129 static void
1130 ephy_extensions_manager_dispose (GObject *object)
1132 EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (object);
1133 EphyExtensionsManagerPrivate *priv = manager->priv;
1135 #ifdef ENABLE_LEGACY_FORMAT
1136 if (priv->xml2ini_xsl != NULL)
1138 xsltFreeStylesheet (priv->xml2ini_xsl);
1139 priv->xml2ini_xsl = NULL;
1141 #endif
1143 if (priv->active_extensions_notifier_id != 0)
1145 eel_gconf_notification_remove (priv->active_extensions_notifier_id);
1146 priv->active_extensions_notifier_id = 0;
1149 if (priv->reload_hash != NULL)
1151 g_hash_table_destroy (priv->reload_hash);
1152 priv->reload_hash = NULL;
1155 if (priv->sync_timeout_id != 0)
1157 g_source_remove (priv->sync_timeout_id);
1158 priv->sync_timeout_id = 0;
1161 if (priv->dir_monitors != NULL)
1163 g_list_foreach (priv->dir_monitors, (GFunc) gnome_vfs_monitor_cancel, NULL);
1164 g_list_free (priv->dir_monitors);
1165 priv->dir_monitors = NULL;
1168 if (priv->extensions != NULL)
1170 g_list_foreach (priv->extensions, (GFunc) g_object_unref, NULL);
1171 g_list_free (priv->extensions);
1172 priv->extensions = NULL;
1175 if (priv->factories != NULL)
1177 /* FIXME release loaded loaders */
1178 g_list_foreach (priv->factories, (GFunc) free_loader_info, NULL);
1179 g_list_free (priv->factories);
1180 priv->factories = NULL;
1183 if (priv->data != NULL)
1185 g_list_foreach (priv->data, (GFunc) free_extension_info, NULL);
1186 g_list_free (priv->data);
1187 priv->data = NULL;
1190 if (priv->windows != NULL)
1192 g_list_free (priv->windows);
1193 priv->windows = NULL;
1196 parent_class->dispose (object);
1199 static void
1200 attach_extension_to_window (EphyExtension *extension,
1201 EphyWindow *window)
1203 attach_window (window, extension);
1206 static void
1207 impl_attach_window (EphyExtension *extension,
1208 EphyWindow *window)
1210 EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (extension);
1212 LOG ("Attach window %p", window);
1214 g_list_foreach (manager->priv->extensions,
1215 (GFunc) attach_extension_to_window, window);
1217 manager->priv->windows = g_list_prepend (manager->priv->windows, window);
1220 static void
1221 impl_detach_window (EphyExtension *extension,
1222 EphyWindow *window)
1224 EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (extension);
1225 GList *tabs, *l;
1227 LOG ("Detach window %p", window);
1229 manager->priv->windows = g_list_remove (manager->priv->windows, window);
1231 g_object_ref (window);
1233 /* Detach tabs (uses impl_detach_tab) */
1234 tabs = ephy_window_get_tabs (window);
1235 for (l = tabs; l; l = l->next)
1237 ephy_extension_detach_tab (extension, window,
1238 EPHY_TAB (l->data));
1240 g_list_free (tabs);
1242 /* Then detach the window */
1243 g_list_foreach (manager->priv->extensions,
1244 (GFunc) ephy_extension_detach_window, window);
1246 g_object_unref (window);
1249 static void
1250 impl_attach_tab (EphyExtension *extension,
1251 EphyWindow *window,
1252 EphyTab *tab)
1254 EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (extension);
1255 GList *l;
1257 LOG ("Attach window %p tab %p", window, tab);
1259 for (l = manager->priv->extensions; l; l = l->next)
1261 ephy_extension_attach_tab (EPHY_EXTENSION (l->data),
1262 window, tab);
1266 static void
1267 impl_detach_tab (EphyExtension *extension,
1268 EphyWindow *window,
1269 EphyTab *tab)
1271 EphyExtensionsManager *manager = EPHY_EXTENSIONS_MANAGER (extension);
1272 GList *l;
1274 LOG ("Detach window %p tab %p", window, tab);
1276 g_object_ref (window);
1277 g_object_ref (tab);
1279 for (l = manager->priv->extensions; l; l = l->next)
1281 ephy_extension_detach_tab (EPHY_EXTENSION (l->data),
1282 window, tab);
1285 g_object_unref (tab);
1286 g_object_unref (window);
1289 static void
1290 ephy_extensions_manager_iface_init (EphyExtensionIface *iface)
1292 iface->attach_window = impl_attach_window;
1293 iface->detach_window = impl_detach_window;
1294 iface->attach_tab = impl_attach_tab;
1295 iface->detach_tab = impl_detach_tab;
1298 static void
1299 ephy_extensions_manager_class_init (EphyExtensionsManagerClass *class)
1301 GObjectClass *object_class = G_OBJECT_CLASS (class);
1303 parent_class = (GObjectClass *) g_type_class_peek_parent (class);
1305 object_class->dispose = ephy_extensions_manager_dispose;
1307 signals[CHANGED] =
1308 g_signal_new ("changed",
1309 G_OBJECT_CLASS_TYPE (object_class),
1310 G_SIGNAL_RUN_FIRST,
1311 G_STRUCT_OFFSET (EphyExtensionsManagerClass, changed),
1312 NULL, NULL,
1313 g_cclosure_marshal_VOID__POINTER,
1314 G_TYPE_NONE,
1316 G_TYPE_POINTER);
1317 signals[ADDED] =
1318 g_signal_new ("added",
1319 G_OBJECT_CLASS_TYPE (object_class),
1320 G_SIGNAL_RUN_FIRST,
1321 G_STRUCT_OFFSET (EphyExtensionsManagerClass, added),
1322 NULL, NULL,
1323 g_cclosure_marshal_VOID__POINTER,
1324 G_TYPE_NONE,
1326 G_TYPE_POINTER);
1327 signals[REMOVED] =
1328 g_signal_new ("removed",
1329 G_OBJECT_CLASS_TYPE (object_class),
1330 G_SIGNAL_RUN_FIRST,
1331 G_STRUCT_OFFSET (EphyExtensionsManagerClass, removed),
1332 NULL, NULL,
1333 g_cclosure_marshal_VOID__POINTER,
1334 G_TYPE_NONE,
1336 G_TYPE_POINTER);
1338 g_type_class_add_private (object_class, sizeof (EphyExtensionsManagerPrivate));