/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995-2002 Spencer Kimball, Peter Mattis, and others * * pikaextensionmanager.c * Copyright (C) 2018 Jehan * * 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 3 of the License, 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, see . */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "libpikaconfig/pikaconfig.h" #include "core-types.h" #include "config/pikacoreconfig.h" #include "pika.h" #include "pikaextension.h" #include "pikaextension-error.h" #include "pikaobject.h" #include "pikamarshal.h" #include "pika-utils.h" #include "pikaextensionmanager.h" #include "pika-intl.h" enum { PROP_0, PROP_PIKA, PROP_BRUSH_PATHS, PROP_DYNAMICS_PATHS, PROP_MYPAINT_BRUSH_PATHS, PROP_PATTERN_PATHS, PROP_GRADIENT_PATHS, PROP_PALETTE_PATHS, PROP_TOOL_PRESET_PATHS, PROP_SPLASH_PATHS, PROP_THEME_PATHS, PROP_PLUG_IN_PATHS, }; enum { EXTENSION_INSTALLED, EXTENSION_REMOVED, LAST_SIGNAL }; struct _PikaExtensionManagerPrivate { Pika *pika; /* Installed system (read-only) extensions. */ GList *sys_extensions; /* Self-installed (read-write) extensions. */ GList *extensions; /* Uninstalled extensions (cached to allow undo). */ GList *uninstalled_extensions; /* Running extensions */ GHashTable *running_extensions; /* Metadata properties */ GList *brush_paths; GList *dynamics_paths; GList *mypaint_brush_paths; GList *pattern_paths; GList *gradient_paths; GList *palette_paths; GList *tool_preset_paths; GList *splash_paths; GList *theme_paths; GList *plug_in_paths; }; static void pika_extension_manager_config_iface_init (PikaConfigInterface *iface); static gboolean pika_extension_manager_serialize (PikaConfig *config, PikaConfigWriter *writer, gpointer data); static gboolean pika_extension_manager_deserialize (PikaConfig *config, GScanner *scanner, gint nest_level, gpointer data); static void pika_extension_manager_finalize (GObject *object); static void pika_extension_manager_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_extension_manager_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_extension_manager_serialize_extension (PikaExtensionManager *manager, PikaExtension *extension, PikaConfigWriter *writer); static void pika_extension_manager_refresh (PikaExtensionManager *manager); static void pika_extension_manager_search_directory (PikaExtensionManager *manager, GFile *directory, gboolean system_dir); static void pika_extension_manager_extension_running (PikaExtension *extension, GParamSpec *pspec, PikaExtensionManager *manager); G_DEFINE_TYPE_WITH_CODE (PikaExtensionManager, pika_extension_manager, PIKA_TYPE_OBJECT, G_ADD_PRIVATE (PikaExtensionManager) G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONFIG, pika_extension_manager_config_iface_init)) #define parent_class pika_extension_manager_parent_class static guint signals[LAST_SIGNAL] = { 0, }; static void pika_extension_manager_class_init (PikaExtensionManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = pika_extension_manager_finalize; object_class->set_property = pika_extension_manager_set_property; object_class->get_property = pika_extension_manager_get_property; g_object_class_install_property (object_class, PROP_PIKA, g_param_spec_object ("pika", NULL, NULL, PIKA_TYPE_PIKA, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_BRUSH_PATHS, g_param_spec_pointer ("brush-paths", NULL, NULL, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_DYNAMICS_PATHS, g_param_spec_pointer ("dynamics-paths", NULL, NULL, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_MYPAINT_BRUSH_PATHS, g_param_spec_pointer ("mypaint-brush-paths", NULL, NULL, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_PATTERN_PATHS, g_param_spec_pointer ("pattern-paths", NULL, NULL, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_GRADIENT_PATHS, g_param_spec_pointer ("gradient-paths", NULL, NULL, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_PALETTE_PATHS, g_param_spec_pointer ("palette-paths", NULL, NULL, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TOOL_PRESET_PATHS, g_param_spec_pointer ("tool-preset-paths", NULL, NULL, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SPLASH_PATHS, g_param_spec_pointer ("splash-paths", NULL, NULL, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_THEME_PATHS, g_param_spec_pointer ("theme-paths", NULL, NULL, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_PLUG_IN_PATHS, g_param_spec_pointer ("plug-in-paths", NULL, NULL, PIKA_PARAM_READWRITE)); signals[EXTENSION_INSTALLED] = g_signal_new ("extension-installed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaExtensionManagerClass, extension_installed), NULL, NULL, pika_marshal_VOID__OBJECT_BOOLEAN, G_TYPE_NONE, 2, PIKA_TYPE_EXTENSION, G_TYPE_BOOLEAN); signals[EXTENSION_REMOVED] = g_signal_new ("extension-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaExtensionManagerClass, extension_removed), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); } static void pika_extension_manager_init (PikaExtensionManager *manager) { manager->p = pika_extension_manager_get_instance_private (manager); manager->p->extensions = NULL; manager->p->sys_extensions = NULL; } static void pika_extension_manager_config_iface_init (PikaConfigInterface *iface) { iface->serialize = pika_extension_manager_serialize; iface->deserialize = pika_extension_manager_deserialize; } static gboolean pika_extension_manager_serialize (PikaConfig *config, PikaConfigWriter *writer, gpointer data G_GNUC_UNUSED) { PikaExtensionManager *manager = PIKA_EXTENSION_MANAGER (config); GList *iter; /* TODO: another information we will want to add will be the last * extension list update to allow ourselves to regularly check for * updates while not doing it too often. */ for (iter = manager->p->extensions; iter; iter = iter->next) pika_extension_manager_serialize_extension (manager, iter->data, writer); for (iter = manager->p->sys_extensions; iter; iter = iter->next) { if (g_list_find_custom (manager->p->extensions, iter->data, (GCompareFunc) pika_extension_cmp)) continue; pika_extension_manager_serialize_extension (manager, iter->data, writer); } return TRUE; } static gboolean pika_extension_manager_deserialize (PikaConfig *config, GScanner *scanner, gint nest_level, gpointer data) { PikaExtensionManager *manager = PIKA_EXTENSION_MANAGER (config); GList **processed = (GList**) data; GTokenType token; token = G_TOKEN_LEFT_PAREN; while (g_scanner_peek_next_token (scanner) == token) { token = g_scanner_get_next_token (scanner); switch (token) { case G_TOKEN_LEFT_PAREN: token = G_TOKEN_IDENTIFIER; break; case G_TOKEN_IDENTIFIER: { gchar *name = NULL; gboolean is_active = FALSE; GType type; type = g_type_from_name (scanner->value.v_identifier); if (! type) { g_scanner_error (scanner, "unable to determine type of '%s'", scanner->value.v_identifier); return FALSE; } if (! g_type_is_a (type, PIKA_TYPE_EXTENSION)) { g_scanner_error (scanner, "'%s' is not a subclass of '%s'", scanner->value.v_identifier, g_type_name (PIKA_TYPE_EXTENSION)); return FALSE; } if (! pika_scanner_parse_string (scanner, &name)) { g_scanner_error (scanner, "Expected extension id not found after PikaExtension."); return FALSE; } if (! name || strlen (name) == 0) { g_scanner_error (scanner, "NULL or empty strings are not valid extension IDs."); if (name) g_free (name); return FALSE; } if (! pika_scanner_parse_token (scanner, G_TOKEN_LEFT_PAREN)) { g_scanner_error (scanner, "Left paren expected after extension ID."); g_free (name); return FALSE; } if (! pika_scanner_parse_identifier (scanner, "active")) { g_scanner_error (scanner, "Expected identifier \"active\" after extension ID."); g_free (name); return FALSE; } if (pika_scanner_parse_boolean (scanner, &is_active)) { if (is_active) { GList *list; list = g_list_find_custom (manager->p->extensions, name, (GCompareFunc) pika_extension_id_cmp); if (! list) list = g_list_find_custom (manager->p->sys_extensions, name, (GCompareFunc) pika_extension_id_cmp); if (list) { GError *error = NULL; if (pika_extension_run (list->data, &error)) { g_hash_table_insert (manager->p->running_extensions, (gpointer) pika_object_get_name (list->data), list->data); } else { g_printerr ("Extension '%s' failed to run: %s\n", pika_object_get_name (list->data), error->message); g_error_free (error); } } } /* Save the list of processed extension IDs, whether * active or not. */ *processed = g_list_prepend (*processed, name); } else { g_scanner_error (scanner, "Expected boolean after \"active\" identifier."); g_free (name); return FALSE; } if (! pika_scanner_parse_token (scanner, G_TOKEN_RIGHT_PAREN)) { g_scanner_error (scanner, "Right paren expected after \"active\" identifier."); return FALSE; } } token = G_TOKEN_RIGHT_PAREN; break; case G_TOKEN_RIGHT_PAREN: token = G_TOKEN_LEFT_PAREN; break; default: /* do nothing */ break; } } return pika_config_deserialize_return (scanner, token, nest_level); } static void pika_extension_manager_finalize (GObject *object) { GList *iter; PikaExtensionManager *manager = PIKA_EXTENSION_MANAGER (object); g_list_free_full (manager->p->sys_extensions, g_object_unref); g_list_free_full (manager->p->extensions, g_object_unref); g_hash_table_unref (manager->p->running_extensions); for (iter = manager->p->uninstalled_extensions; iter; iter = iter->next) { /* Recursively delete folders of uninstalled extensions. */ GError *error = NULL; GFile *file; file = g_file_new_for_path (pika_extension_get_path (iter->data)); if (! pika_file_delete_recursive (file, &error)) g_warning ("%s: %s\n", G_STRFUNC, error->message); g_object_unref (file); } g_list_free_full (manager->p->uninstalled_extensions, g_object_unref); g_list_free_full (manager->p->brush_paths, g_object_unref); manager->p->brush_paths = NULL; g_list_free_full (manager->p->dynamics_paths, g_object_unref); manager->p->dynamics_paths = NULL; g_list_free_full (manager->p->mypaint_brush_paths, g_object_unref); manager->p->brush_paths = NULL; g_list_free_full (manager->p->pattern_paths, g_object_unref); manager->p->pattern_paths = NULL; g_list_free_full (manager->p->gradient_paths, g_object_unref); manager->p->gradient_paths = NULL; g_list_free_full (manager->p->palette_paths, g_object_unref); manager->p->palette_paths = NULL; g_list_free_full (manager->p->tool_preset_paths, g_object_unref); manager->p->tool_preset_paths = NULL; g_list_free_full (manager->p->plug_in_paths, g_object_unref); manager->p->plug_in_paths = NULL; g_list_free_full (manager->p->splash_paths, g_object_unref); manager->p->splash_paths = NULL; g_list_free_full (manager->p->theme_paths, g_object_unref); manager->p->theme_paths = NULL; G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_extension_manager_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaExtensionManager *manager = PIKA_EXTENSION_MANAGER (object); switch (property_id) { case PROP_PIKA: manager->p->pika = g_value_get_object (value); break; case PROP_BRUSH_PATHS: g_list_free_full (manager->p->brush_paths, g_object_unref); manager->p->brush_paths = g_value_get_pointer (value); break; case PROP_DYNAMICS_PATHS: g_list_free_full (manager->p->dynamics_paths, g_object_unref); manager->p->dynamics_paths = g_value_get_pointer (value); break; case PROP_MYPAINT_BRUSH_PATHS: g_list_free_full (manager->p->mypaint_brush_paths, g_object_unref); manager->p->mypaint_brush_paths = g_value_get_pointer (value); break; case PROP_PATTERN_PATHS: g_list_free_full (manager->p->pattern_paths, g_object_unref); manager->p->pattern_paths = g_value_get_pointer (value); break; case PROP_GRADIENT_PATHS: g_list_free_full (manager->p->gradient_paths, g_object_unref); manager->p->gradient_paths = g_value_get_pointer (value); break; case PROP_PALETTE_PATHS: g_list_free_full (manager->p->palette_paths, g_object_unref); manager->p->palette_paths = g_value_get_pointer (value); break; case PROP_TOOL_PRESET_PATHS: g_list_free_full (manager->p->tool_preset_paths, g_object_unref); manager->p->tool_preset_paths = g_value_get_pointer (value); break; case PROP_SPLASH_PATHS: g_list_free_full (manager->p->splash_paths, g_object_unref); manager->p->splash_paths = g_value_get_pointer (value); break; case PROP_THEME_PATHS: g_list_free_full (manager->p->theme_paths, g_object_unref); manager->p->theme_paths = g_value_get_pointer (value); break; case PROP_PLUG_IN_PATHS: g_list_free_full (manager->p->plug_in_paths, g_object_unref); manager->p->plug_in_paths = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_extension_manager_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaExtensionManager *manager = PIKA_EXTENSION_MANAGER (object); switch (property_id) { case PROP_PIKA: g_value_set_object (value, manager->p->pika); break; case PROP_BRUSH_PATHS: g_value_set_pointer (value, manager->p->brush_paths); break; case PROP_DYNAMICS_PATHS: g_value_set_pointer (value, manager->p->dynamics_paths); break; case PROP_MYPAINT_BRUSH_PATHS: g_value_set_pointer (value, manager->p->mypaint_brush_paths); break; case PROP_PATTERN_PATHS: g_value_set_pointer (value, manager->p->pattern_paths); break; case PROP_GRADIENT_PATHS: g_value_set_pointer (value, manager->p->gradient_paths); break; case PROP_PALETTE_PATHS: g_value_set_pointer (value, manager->p->palette_paths); break; case PROP_TOOL_PRESET_PATHS: g_value_set_pointer (value, manager->p->tool_preset_paths); break; case PROP_SPLASH_PATHS: g_value_set_pointer (value, manager->p->splash_paths); break; case PROP_THEME_PATHS: g_value_set_pointer (value, manager->p->theme_paths); break; case PROP_PLUG_IN_PATHS: g_value_set_pointer (value, manager->p->plug_in_paths); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /* Public functions. */ PikaExtensionManager * pika_extension_manager_new (Pika *pika) { PikaExtensionManager *manager; manager = g_object_new (PIKA_TYPE_EXTENSION_MANAGER, "pika", pika, NULL); return manager; } void pika_extension_manager_initialize (PikaExtensionManager *manager) { GFile *file; GError *error = NULL; gchar *path_str; GList *path; GList *list; GList *processed_ids; g_return_if_fail (PIKA_IS_EXTENSION_MANAGER (manager)); /* List user-installed extensions. */ path_str = pika_config_build_writable_path ("extensions"); path = pika_config_path_expand_to_files (path_str, NULL); g_free (path_str); for (list = path; list; list = g_list_next (list)) pika_extension_manager_search_directory (manager, list->data, FALSE); g_list_free_full (path, (GDestroyNotify) g_object_unref); /* List system extensions. */ path_str = pika_config_build_system_path ("extensions"); path = pika_config_path_expand_to_files (path_str, NULL); g_free (path_str); for (list = path; list; list = g_list_next (list)) pika_extension_manager_search_directory (manager, list->data, TRUE); g_list_free_full (path, (GDestroyNotify) g_object_unref); /* Actually load the extensions. */ if (manager->p->running_extensions) g_hash_table_unref (manager->p->running_extensions); manager->p->running_extensions = g_hash_table_new (g_str_hash, g_str_equal); file = pika_directory_file ("extensionrc", NULL); processed_ids = NULL; if (g_file_query_exists (file, NULL)) { if (manager->p->pika->be_verbose) g_print ("Parsing '%s'\n", pika_file_get_utf8_name (file)); pika_config_deserialize_file (PIKA_CONFIG (manager), file, &processed_ids, &error); if (error) { g_printerr ("Failed to parse '%s': %s\n", pika_file_get_utf8_name (file), error->message); g_error_free (error); } } g_object_unref (file); /* System extensions are on by default and user ones are off by * default */ for (list = manager->p->extensions; list; list = list->next) { /* Directly flag user-installed extensions as processed. We do not * load them unless they were explicitly loaded (i.e. they must be * set in extensionrc. */ if (! g_list_find_custom (processed_ids, pika_object_get_name (list->data), (GCompareFunc) g_strcmp0)) processed_ids = g_list_prepend (processed_ids, g_strdup (pika_object_get_name (list->data))); g_signal_connect (list->data, "notify::running", G_CALLBACK (pika_extension_manager_extension_running), manager); } for (list = manager->p->sys_extensions; list; list = g_list_next (list)) { /* Unlike user-installed extensions, system extensions are loaded * by default if they were not set in the extensionrc (so that new * extensions installed with PIKA updates get loaded) and if they * were not overridden by a user-installed extension (same ID). */ if (! g_list_find_custom (processed_ids, pika_object_get_name (list->data), (GCompareFunc) g_strcmp0)) { error = NULL; if (pika_extension_run (list->data, &error)) { g_hash_table_insert (manager->p->running_extensions, (gpointer) pika_object_get_name (list->data), list->data); } else { g_printerr ("Extension '%s' failed to run: %s\n", pika_object_get_name (list->data), error->message); g_error_free (error); } } g_signal_connect (list->data, "notify::running", G_CALLBACK (pika_extension_manager_extension_running), manager); } pika_extension_manager_refresh (manager); g_list_free_full (processed_ids, g_free); } void pika_extension_manager_exit (PikaExtensionManager *manager) { GFile *file; GError *error = NULL; g_return_if_fail (PIKA_IS_EXTENSION_MANAGER (manager)); file = pika_directory_file ("extensionrc", NULL); if (manager->p->pika->be_verbose) g_print ("Writing '%s'\n", pika_file_get_utf8_name (file)); if (! pika_config_serialize_to_file (PIKA_CONFIG (manager), file, "PIKA extensionrc", "end of extensionrc", NULL, &error)) { pika_message_literal (manager->p->pika, NULL, PIKA_MESSAGE_ERROR, error->message); g_error_free (error); } g_object_unref (file); } const GList * pika_extension_manager_get_system_extensions (PikaExtensionManager *manager) { return manager->p->sys_extensions; } const GList * pika_extension_manager_get_user_extensions (PikaExtensionManager *manager) { return manager->p->extensions; } /** * pika_extension_manager_is_running: * @extension: * * Returns: %TRUE if @extension is ON. */ gboolean pika_extension_manager_is_running (PikaExtensionManager *manager, PikaExtension *extension) { PikaExtension *ext; ext = g_hash_table_lookup (manager->p->running_extensions, pika_object_get_name (extension)); return (ext && ext == extension); } /** * pika_extension_manager_can_run: * @extension: * * Returns: %TRUE is @extension can be run. */ gboolean pika_extension_manager_can_run (PikaExtensionManager *manager, PikaExtension *extension) { /* System extension overridden by another extension. */ if (g_list_find (manager->p->sys_extensions, extension) && g_list_find_custom (manager->p->extensions, extension, (GCompareFunc) pika_extension_cmp)) return FALSE; /* TODO: should return FALSE if required PIKA version or other * requirements are not filled as well. */ return TRUE; } /** * pika_extension_manager_is_removed: * @manager: * @extension: * * Returns: %TRUE is @extension was installed and has been removed * (hence pika_extension_manager_undo_remove() can be used on it). */ gboolean pika_extension_manager_is_removed (PikaExtensionManager *manager, PikaExtension *extension) { GList *iter; g_return_val_if_fail (PIKA_IS_EXTENSION_MANAGER (manager), FALSE); g_return_val_if_fail (PIKA_IS_EXTENSION (extension), FALSE); iter = manager->p->uninstalled_extensions; for (; iter; iter = iter->next) if (pika_extension_cmp (iter->data, extension) == 0) break; return (iter != NULL); } /** * pika_extension_manager_install: * @manager: * @extension: * @error: * * Install @extension. This merely adds the extension in the known * extension list to make the manager aware of it at runtime, and to * emit a signal for GUI update. */ gboolean pika_extension_manager_install (PikaExtensionManager *manager, PikaExtension *extension, GError **error) { gboolean success = FALSE; if ((success = pika_extension_load (extension, error))) { manager->p->extensions = g_list_prepend (manager->p->extensions, extension); g_signal_connect (extension, "notify::running", G_CALLBACK (pika_extension_manager_extension_running), manager); g_signal_emit (manager, signals[EXTENSION_INSTALLED], 0, extension, FALSE); } return success; } /** * pika_extension_manager_remove: * @manager: * @extension: * @error: * * Uninstall @extension. Technically this only move the object to a * temporary list. The extension folder will be really deleted when PIKA * will stop. * This allows to undo a deletion for as long as the session runs. */ gboolean pika_extension_manager_remove (PikaExtensionManager *manager, PikaExtension *extension, GError **error) { GList *iter; g_return_val_if_fail (PIKA_IS_EXTENSION_MANAGER (manager), FALSE); g_return_val_if_fail (PIKA_IS_EXTENSION (extension), FALSE); iter = (GList *) pika_extension_manager_get_system_extensions (manager); for (; iter; iter = iter->next) if (iter->data == extension) { /* System extensions cannot be uninstalled. */ if (error) *error = g_error_new (PIKA_EXTENSION_ERROR, PIKA_EXTENSION_FAILED, _("System extensions cannot be uninstalled.")); return FALSE; } iter = (GList *) pika_extension_manager_get_user_extensions (manager); for (; iter; iter = iter->next) if (pika_extension_cmp (iter->data, extension) == 0) break; /* The extension has to be in the extension list. */ g_return_val_if_fail (iter != NULL, FALSE); pika_extension_stop (extension); manager->p->extensions = g_list_remove_link (manager->p->extensions, iter); manager->p->uninstalled_extensions = g_list_concat (manager->p->uninstalled_extensions, iter); g_signal_emit (manager, signals[EXTENSION_REMOVED], 0, pika_object_get_name (extension)); return TRUE; } gboolean pika_extension_manager_undo_remove (PikaExtensionManager *manager, PikaExtension *extension, GError **error) { GList *iter; g_return_val_if_fail (PIKA_IS_EXTENSION_MANAGER (manager), FALSE); g_return_val_if_fail (PIKA_IS_EXTENSION (extension), FALSE); iter = manager->p->uninstalled_extensions; for (; iter; iter = iter->next) if (pika_extension_cmp (iter->data, extension) == 0) break; /* The extension has to be in the uninstalled extension list. */ g_return_val_if_fail (iter != NULL, FALSE); manager->p->uninstalled_extensions = g_list_remove (manager->p->uninstalled_extensions, extension); pika_extension_manager_install (manager, extension, error); return TRUE; } /* Private functions. */ static void pika_extension_manager_serialize_extension (PikaExtensionManager *manager, PikaExtension *extension, PikaConfigWriter *writer) { const gchar *name = pika_object_get_name (extension); g_return_if_fail (name != NULL); pika_config_writer_open (writer, g_type_name (G_TYPE_FROM_INSTANCE (extension))); pika_config_writer_string (writer, name); /* The extensionrc does not need to save any information about an * extension. This is all saved in the metadata. The only thing we * care to store is the state of extensions, i.e. which one is loaded * or not (since you can install an extension but leave it unloaded), * named by its unique ID. * As a consequence, PikaExtension does not need to implement * PikaConfigInterface. */ pika_config_writer_open (writer, "active"); if (g_hash_table_contains (manager->p->running_extensions, name)) pika_config_writer_identifier (writer, "yes"); else pika_config_writer_identifier (writer, "no"); pika_config_writer_close (writer); pika_config_writer_close (writer); } static void pika_extension_manager_refresh (PikaExtensionManager *manager) { GHashTableIter iter; gpointer key; gpointer value; GList *brush_paths = NULL; GList *dynamics_paths = NULL; GList *mypaint_brush_paths = NULL; GList *pattern_paths = NULL; GList *gradient_paths = NULL; GList *palette_paths = NULL; GList *tool_preset_paths = NULL; GList *splash_paths = NULL; GList *theme_paths = NULL; GList *plug_in_paths = NULL; g_hash_table_iter_init (&iter, manager->p->running_extensions); while (g_hash_table_iter_next (&iter, &key, &value)) { PikaExtension *extension = value; GList *new_paths; new_paths = g_list_copy_deep (pika_extension_get_brush_paths (extension), (GCopyFunc) g_object_ref, NULL); brush_paths = g_list_concat (brush_paths, new_paths); new_paths = g_list_copy_deep (pika_extension_get_dynamics_paths (extension), (GCopyFunc) g_object_ref, NULL); dynamics_paths = g_list_concat (dynamics_paths, new_paths); new_paths = g_list_copy_deep (pika_extension_get_mypaint_brush_paths (extension), (GCopyFunc) g_object_ref, NULL); mypaint_brush_paths = g_list_concat (mypaint_brush_paths, new_paths); new_paths = g_list_copy_deep (pika_extension_get_pattern_paths (extension), (GCopyFunc) g_object_ref, NULL); pattern_paths = g_list_concat (pattern_paths, new_paths); new_paths = g_list_copy_deep (pika_extension_get_gradient_paths (extension), (GCopyFunc) g_object_ref, NULL); gradient_paths = g_list_concat (gradient_paths, new_paths); new_paths = g_list_copy_deep (pika_extension_get_palette_paths (extension), (GCopyFunc) g_object_ref, NULL); palette_paths = g_list_concat (palette_paths, new_paths); new_paths = g_list_copy_deep (pika_extension_get_tool_preset_paths (extension), (GCopyFunc) g_object_ref, NULL); tool_preset_paths = g_list_concat (tool_preset_paths, new_paths); new_paths = g_list_copy_deep (pika_extension_get_splash_paths (extension), (GCopyFunc) g_object_ref, NULL); splash_paths = g_list_concat (splash_paths, new_paths); new_paths = g_list_copy_deep (pika_extension_get_theme_paths (extension), (GCopyFunc) g_object_ref, NULL); theme_paths = g_list_concat (theme_paths, new_paths); new_paths = g_list_copy_deep (pika_extension_get_plug_in_paths (extension), (GCopyFunc) g_object_ref, NULL); plug_in_paths = g_list_concat (plug_in_paths, new_paths); } g_object_set (manager, "brush-paths", brush_paths, "dynamics-paths", dynamics_paths, "mypaint-brush-paths", brush_paths, "pattern-paths", pattern_paths, "gradient-paths", gradient_paths, "palette-paths", palette_paths, "tool-preset-paths", tool_preset_paths, "plug-in-paths", plug_in_paths, "splash-paths", splash_paths, "theme-paths", theme_paths, NULL); } static void pika_extension_manager_search_directory (PikaExtensionManager *manager, GFile *directory, gboolean system_dir) { GFileEnumerator *enumerator; enumerator = g_file_enumerate_children (directory, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (enumerator) { GFileInfo *info; while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL))) { GFile *subdir; if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) { g_object_unref (info); continue; } subdir = g_file_enumerator_get_child (enumerator, info); if (g_file_query_file_type (subdir, G_FILE_QUERY_INFO_NONE, NULL) == G_FILE_TYPE_DIRECTORY) { PikaExtension *extension; GError *error = NULL; extension = pika_extension_new (g_file_peek_path (subdir), ! system_dir); if (pika_extension_load (extension, &error)) { if (system_dir) manager->p->sys_extensions = g_list_prepend (manager->p->sys_extensions, extension); else manager->p->extensions = g_list_prepend (manager->p->extensions, extension); } else { g_object_unref (extension); if (error) { g_printerr (_("Skipping extension '%s': %s\n"), g_file_peek_path (subdir), error->message); g_error_free (error); } } } else { g_printerr (_("Skipping unknown file '%s' in extension directory.\n"), g_file_peek_path (subdir)); } g_object_unref (subdir); g_object_unref (info); } g_object_unref (enumerator); } } static void pika_extension_manager_extension_running (PikaExtension *extension, GParamSpec *pspec, PikaExtensionManager *manager) { gboolean running; g_object_get (extension, "running", &running, NULL); if (running) g_hash_table_insert (manager->p->running_extensions, (gpointer) pika_object_get_name (extension), extension); else g_hash_table_remove (manager->p->running_extensions, (gpointer) pika_object_get_name (extension)); pika_extension_manager_refresh (manager); }