PIKApp/app/core/pikaextensionmanager.c

1097 lines
40 KiB
C

/* 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 <jehan@gimp.org>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#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);
}