1097 lines
40 KiB
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_is_hidden (info))
|
||
|
{
|
||
|
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);
|
||
|
}
|