844 lines
26 KiB
C
844 lines
26 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 Spencer Kimball and Peter Mattis
|
||
|
*
|
||
|
* pikaextension.c
|
||
|
* Copyright (C) 2018 Jehan <jehan@girinstud.io>
|
||
|
*
|
||
|
* 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 <appstream-glib.h>
|
||
|
#include <gegl.h>
|
||
|
|
||
|
#include "libpikabase/pikabase.h"
|
||
|
|
||
|
#include "core-types.h"
|
||
|
|
||
|
#include "pika-utils.h"
|
||
|
#include "pikaerror.h"
|
||
|
#include "pikaextension.h"
|
||
|
#include "pikaextension-error.h"
|
||
|
|
||
|
#include "pika-intl.h"
|
||
|
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
PROP_0,
|
||
|
PROP_PATH,
|
||
|
PROP_WRITABLE,
|
||
|
PROP_RUNNING
|
||
|
};
|
||
|
|
||
|
struct _PikaExtensionPrivate
|
||
|
{
|
||
|
gchar *path;
|
||
|
|
||
|
AsApp *app;
|
||
|
gboolean writable;
|
||
|
gboolean running;
|
||
|
|
||
|
/* Extension metadata: directories. */
|
||
|
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;
|
||
|
|
||
|
/* Extension metadata: plug-in entry points. */
|
||
|
GList *plug_in_paths;
|
||
|
};
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
GString *text;
|
||
|
gint level;
|
||
|
|
||
|
gboolean numbered_list;
|
||
|
gint list_num;
|
||
|
gboolean unnumbered_list;
|
||
|
|
||
|
const gchar *lang;
|
||
|
GString *original;
|
||
|
gint foreign_level;
|
||
|
} ParseState;
|
||
|
|
||
|
|
||
|
static void pika_extension_finalize (GObject *object);
|
||
|
static void pika_extension_set_property (GObject *object,
|
||
|
guint property_id,
|
||
|
const GValue *value,
|
||
|
GParamSpec *pspec);
|
||
|
static void pika_extension_get_property (GObject *object,
|
||
|
guint property_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec);
|
||
|
|
||
|
static void pika_extension_clean (PikaExtension *extension);
|
||
|
static gint pika_extension_file_cmp (GFile *a,
|
||
|
GFile *b);
|
||
|
static GList * pika_extension_validate_paths (PikaExtension *extension,
|
||
|
const gchar *paths,
|
||
|
gboolean as_directories,
|
||
|
GError **error);
|
||
|
|
||
|
|
||
|
G_DEFINE_TYPE_WITH_PRIVATE (PikaExtension, pika_extension, PIKA_TYPE_OBJECT)
|
||
|
|
||
|
#define parent_class pika_extension_parent_class
|
||
|
|
||
|
|
||
|
static void
|
||
|
pika_extension_class_init (PikaExtensionClass *klass)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
|
||
|
object_class->finalize = pika_extension_finalize;
|
||
|
object_class->set_property = pika_extension_set_property;
|
||
|
object_class->get_property = pika_extension_get_property;
|
||
|
|
||
|
g_object_class_install_property (object_class, PROP_PATH,
|
||
|
g_param_spec_string ("path",
|
||
|
NULL, NULL, NULL,
|
||
|
PIKA_PARAM_READWRITE |
|
||
|
G_PARAM_CONSTRUCT_ONLY));
|
||
|
g_object_class_install_property (object_class, PROP_WRITABLE,
|
||
|
g_param_spec_boolean ("writable",
|
||
|
NULL, NULL, FALSE,
|
||
|
PIKA_PARAM_READWRITE |
|
||
|
G_PARAM_CONSTRUCT_ONLY));
|
||
|
g_object_class_install_property (object_class, PROP_RUNNING,
|
||
|
g_param_spec_boolean ("running",
|
||
|
NULL, NULL, FALSE,
|
||
|
PIKA_PARAM_READWRITE));
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_extension_init (PikaExtension *extension)
|
||
|
{
|
||
|
extension->p = pika_extension_get_instance_private (extension);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_extension_finalize (GObject *object)
|
||
|
{
|
||
|
PikaExtension *extension = PIKA_EXTENSION (object);
|
||
|
|
||
|
pika_extension_clean (extension);
|
||
|
|
||
|
g_free (extension->p->path);
|
||
|
if (extension->p->app)
|
||
|
g_object_unref (extension->p->app);
|
||
|
|
||
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_extension_set_property (GObject *object,
|
||
|
guint property_id,
|
||
|
const GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
PikaExtension *extension = PIKA_EXTENSION (object);
|
||
|
|
||
|
switch (property_id)
|
||
|
{
|
||
|
case PROP_PATH:
|
||
|
g_free (extension->p->path);
|
||
|
extension->p->path = g_value_dup_string (value);
|
||
|
pika_object_take_name (PIKA_OBJECT (object),
|
||
|
g_path_get_basename (extension->p->path));
|
||
|
break;
|
||
|
case PROP_WRITABLE:
|
||
|
extension->p->writable = g_value_get_boolean (value);
|
||
|
break;
|
||
|
case PROP_RUNNING:
|
||
|
extension->p->running = g_value_get_boolean (value);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_extension_get_property (GObject *object,
|
||
|
guint property_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
PikaExtension *extension = PIKA_EXTENSION (object);
|
||
|
|
||
|
switch (property_id)
|
||
|
{
|
||
|
case PROP_PATH:
|
||
|
g_value_set_string (value, extension->p->path);
|
||
|
break;
|
||
|
case PROP_WRITABLE:
|
||
|
g_value_set_boolean (value, extension->p->writable);
|
||
|
break;
|
||
|
case PROP_RUNNING:
|
||
|
g_value_set_boolean (value, extension->p->running);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* public functions */
|
||
|
|
||
|
PikaExtension *
|
||
|
pika_extension_new (const gchar *dir,
|
||
|
gboolean writable)
|
||
|
{
|
||
|
g_return_val_if_fail (dir && g_file_test (dir, G_FILE_TEST_IS_DIR), NULL);
|
||
|
|
||
|
return g_object_new (PIKA_TYPE_EXTENSION,
|
||
|
"path", dir,
|
||
|
"writable", writable,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
const gchar *
|
||
|
pika_extension_get_name (PikaExtension *extension)
|
||
|
{
|
||
|
g_return_val_if_fail (extension->p->app != NULL, NULL);
|
||
|
|
||
|
return as_app_get_name (extension->p->app, g_getenv ("LANGUAGE")) ?
|
||
|
as_app_get_name (extension->p->app, g_getenv ("LANGUAGE")) :
|
||
|
as_app_get_name (extension->p->app, NULL);
|
||
|
}
|
||
|
|
||
|
const gchar *
|
||
|
pika_extension_get_comment (PikaExtension *extension)
|
||
|
{
|
||
|
g_return_val_if_fail (extension->p->app != NULL, NULL);
|
||
|
|
||
|
return as_app_get_comment (extension->p->app, g_getenv ("LANGUAGE")) ?
|
||
|
as_app_get_comment (extension->p->app, g_getenv ("LANGUAGE")) :
|
||
|
as_app_get_comment (extension->p->app, NULL);
|
||
|
}
|
||
|
|
||
|
const gchar *
|
||
|
pika_extension_get_description (PikaExtension *extension)
|
||
|
{
|
||
|
g_return_val_if_fail (extension->p->app != NULL, NULL);
|
||
|
|
||
|
return as_app_get_description (extension->p->app, g_getenv ("LANGUAGE")) ?
|
||
|
as_app_get_description (extension->p->app, g_getenv ("LANGUAGE")) :
|
||
|
as_app_get_description (extension->p->app, NULL);
|
||
|
}
|
||
|
|
||
|
GdkPixbuf *
|
||
|
pika_extension_get_screenshot (PikaExtension *extension,
|
||
|
gint width,
|
||
|
gint height,
|
||
|
const gchar **caption)
|
||
|
{
|
||
|
GdkPixbuf *pixbuf = NULL;
|
||
|
AsScreenshot *screenshot;
|
||
|
|
||
|
g_return_val_if_fail (extension->p->app != NULL, NULL);
|
||
|
|
||
|
screenshot = as_app_get_screenshot_default (extension->p->app);
|
||
|
if (screenshot)
|
||
|
{
|
||
|
AsImage *image;
|
||
|
|
||
|
image = as_screenshot_get_image_for_locale (screenshot, g_getenv ("LANGUAGE"), width, height);
|
||
|
if (! image)
|
||
|
image = as_screenshot_get_image_for_locale (screenshot, NULL, width, height);
|
||
|
|
||
|
pixbuf = as_image_get_pixbuf (image);
|
||
|
if (pixbuf)
|
||
|
{
|
||
|
g_object_ref (pixbuf);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GFile *file;
|
||
|
GFileInputStream *istream;
|
||
|
GError *error = NULL;
|
||
|
|
||
|
file = g_file_new_for_uri (as_image_get_url (image));
|
||
|
istream = g_file_read (file, NULL, &error);
|
||
|
if (istream)
|
||
|
{
|
||
|
pixbuf = gdk_pixbuf_new_from_stream (G_INPUT_STREAM (istream), NULL, &error);
|
||
|
g_object_unref (istream);
|
||
|
}
|
||
|
|
||
|
if (error)
|
||
|
{
|
||
|
g_printerr ("%s: %s\n", G_STRFUNC, error->message);
|
||
|
g_error_free (error);
|
||
|
}
|
||
|
g_object_unref (file);
|
||
|
}
|
||
|
|
||
|
if (caption)
|
||
|
{
|
||
|
*caption = as_screenshot_get_caption (screenshot, g_getenv ("LANGUAGE"));
|
||
|
if (*caption == NULL)
|
||
|
*caption = as_screenshot_get_caption (screenshot, NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return pixbuf;
|
||
|
}
|
||
|
|
||
|
const gchar *
|
||
|
pika_extension_get_path (PikaExtension *extension)
|
||
|
{
|
||
|
g_return_val_if_fail (PIKA_IS_EXTENSION (extension), NULL);
|
||
|
|
||
|
return extension->p->path;
|
||
|
}
|
||
|
|
||
|
gchar *
|
||
|
pika_extension_get_markup_description (PikaExtension *extension)
|
||
|
{
|
||
|
const gchar *description;
|
||
|
|
||
|
g_return_val_if_fail (PIKA_IS_EXTENSION (extension), NULL);
|
||
|
|
||
|
description = pika_extension_get_description (extension);
|
||
|
|
||
|
return pika_appstream_to_pango_markup (description);
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
pika_extension_load (PikaExtension *extension,
|
||
|
GError **error)
|
||
|
{
|
||
|
AsApp *app;
|
||
|
GPtrArray *extends;
|
||
|
GPtrArray *requires;
|
||
|
AsRelease *release;
|
||
|
gchar *appdata_name;
|
||
|
gchar *path;
|
||
|
gboolean success = FALSE;
|
||
|
gboolean has_require = FALSE;
|
||
|
|
||
|
g_clear_object (&extension->p->app);
|
||
|
|
||
|
/* Search in subdirectory if a file with the same name as
|
||
|
* directory and ending with ".metainfo.xml" exists.
|
||
|
*/
|
||
|
appdata_name = g_strdup_printf ("%s.metainfo.xml",
|
||
|
pika_object_get_name (PIKA_OBJECT (extension)));
|
||
|
path = g_build_filename (extension->p->path, appdata_name, NULL);
|
||
|
g_free (appdata_name);
|
||
|
|
||
|
app = as_app_new ();
|
||
|
success = as_app_parse_file (app, path,
|
||
|
AS_APP_PARSE_FLAG_USE_HEURISTICS,
|
||
|
error);
|
||
|
g_free (path);
|
||
|
if (success && as_app_get_kind (app) != AS_APP_KIND_ADDON)
|
||
|
{
|
||
|
/* Properly setting the type will allow extensions to be
|
||
|
* distributed appropriately through other means.
|
||
|
*/
|
||
|
if (error && *error == NULL)
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_BAD_APPDATA,
|
||
|
_("Extension AppData must be of type \"addon\", found \"%s\" instead."),
|
||
|
as_app_kind_to_string (as_app_get_kind (app)));
|
||
|
success = FALSE;
|
||
|
}
|
||
|
|
||
|
extends = as_app_get_extends (app);
|
||
|
if (success &&
|
||
|
! g_ptr_array_find_with_equal_func (extends, "technology.heckin.PIKA",
|
||
|
g_str_equal, NULL))
|
||
|
{
|
||
|
/* Properly setting the <extends> will allow extensions to be
|
||
|
* distributed appropriately through other means.
|
||
|
*/
|
||
|
if (error && *error == NULL)
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_BAD_APPDATA,
|
||
|
_("Extension AppData must extend \"technology.heckin.PIKA\"."));
|
||
|
success = FALSE;
|
||
|
}
|
||
|
|
||
|
if (success &&
|
||
|
g_strcmp0 (as_app_get_id (app),
|
||
|
pika_object_get_name (extension)) != 0)
|
||
|
{
|
||
|
/* Extension IDs will be unique and we want therefore the
|
||
|
* installation folder to sync in order to avoid path clashes.
|
||
|
*/
|
||
|
if (error && *error == NULL)
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_FAILED,
|
||
|
_("Extension AppData id (\"%s\") and directory (\"%s\") must be the same."),
|
||
|
as_app_get_id (app), pika_object_get_name (extension));
|
||
|
success = FALSE;
|
||
|
}
|
||
|
|
||
|
release = as_app_get_release_default (app);
|
||
|
if (success && (! release || ! as_release_get_version (release)))
|
||
|
{
|
||
|
/* We don't need the detail, just to know that the extension has a
|
||
|
* release tag with a version. This is very important since it is
|
||
|
* the only way we can manage updates.
|
||
|
*/
|
||
|
if (error && *error == NULL)
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_NO_VERSION,
|
||
|
_("Extension AppData must advertise a version in a <release> tag."));
|
||
|
success = FALSE;
|
||
|
}
|
||
|
|
||
|
requires = as_app_get_requires (app);
|
||
|
if (success && requires)
|
||
|
{
|
||
|
gint i;
|
||
|
|
||
|
/* An extension could set requirements, in particular a range of
|
||
|
* supported version of PIKA, but also other extensions.
|
||
|
*/
|
||
|
|
||
|
for (i = 0; i < requires->len; i++)
|
||
|
{
|
||
|
AsRequire *require = g_ptr_array_index (requires, i);
|
||
|
|
||
|
if (as_require_get_kind (require) == AS_REQUIRE_KIND_ID &&
|
||
|
g_strcmp0 (as_require_get_value (require), "technology.heckin.PIKA") == 0)
|
||
|
{
|
||
|
has_require = TRUE;
|
||
|
if (! as_require_version_compare (require, PIKA_VERSION, error))
|
||
|
{
|
||
|
success = FALSE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else if (error && *error == NULL)
|
||
|
{
|
||
|
/* Right now we only support requirement relative to PIKA
|
||
|
* version.
|
||
|
*/
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_FAILED,
|
||
|
_("Unsupported <requires> \"%s\" (type %s)."),
|
||
|
as_require_get_value (require),
|
||
|
as_require_kind_to_string (as_require_get_kind (require)));
|
||
|
success = FALSE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (! has_require)
|
||
|
{
|
||
|
success = FALSE;
|
||
|
if (error && *error == NULL)
|
||
|
{
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_FAILED,
|
||
|
_("<requires><id>technology.heckin.PIKA</id></requires> for version comparison is mandatory."));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (success)
|
||
|
extension->p->app = app;
|
||
|
else
|
||
|
g_object_unref (app);
|
||
|
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
pika_extension_run (PikaExtension *extension,
|
||
|
GError **error)
|
||
|
{
|
||
|
GHashTable *metadata;
|
||
|
gchar *value;
|
||
|
|
||
|
g_return_val_if_fail (extension->p->app != NULL, FALSE);
|
||
|
g_return_val_if_fail (error && *error == NULL, FALSE);
|
||
|
|
||
|
pika_extension_clean (extension);
|
||
|
metadata = as_app_get_metadata (extension->p->app);
|
||
|
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::brush-path");
|
||
|
extension->p->brush_paths = pika_extension_validate_paths (extension,
|
||
|
value, TRUE,
|
||
|
error);
|
||
|
|
||
|
if (! (*error))
|
||
|
{
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::dynamics-path");
|
||
|
extension->p->dynamics_paths = pika_extension_validate_paths (extension,
|
||
|
value, TRUE,
|
||
|
error);
|
||
|
}
|
||
|
if (! (*error))
|
||
|
{
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::mypaint-brush-path");
|
||
|
extension->p->mypaint_brush_paths = pika_extension_validate_paths (extension,
|
||
|
value, TRUE,
|
||
|
error);
|
||
|
}
|
||
|
if (! (*error))
|
||
|
{
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::pattern-path");
|
||
|
extension->p->pattern_paths = pika_extension_validate_paths (extension,
|
||
|
value, TRUE,
|
||
|
error);
|
||
|
}
|
||
|
if (! (*error))
|
||
|
{
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::gradient-path");
|
||
|
extension->p->gradient_paths = pika_extension_validate_paths (extension,
|
||
|
value, TRUE,
|
||
|
error);
|
||
|
}
|
||
|
if (! (*error))
|
||
|
{
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::palette-path");
|
||
|
extension->p->palette_paths = pika_extension_validate_paths (extension,
|
||
|
value, TRUE,
|
||
|
error);
|
||
|
}
|
||
|
if (! (*error))
|
||
|
{
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::tool-preset-path");
|
||
|
extension->p->tool_preset_paths = pika_extension_validate_paths (extension,
|
||
|
value, TRUE,
|
||
|
error);
|
||
|
}
|
||
|
if (! (*error))
|
||
|
{
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::plug-in-path");
|
||
|
extension->p->plug_in_paths = pika_extension_validate_paths (extension,
|
||
|
value, FALSE,
|
||
|
error);
|
||
|
}
|
||
|
if (! (*error))
|
||
|
{
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::splash-path");
|
||
|
extension->p->splash_paths = pika_extension_validate_paths (extension,
|
||
|
value, TRUE,
|
||
|
error);
|
||
|
}
|
||
|
if (! (*error))
|
||
|
{
|
||
|
value = g_hash_table_lookup (metadata, "PIKA::theme-path");
|
||
|
extension->p->theme_paths = pika_extension_validate_paths (extension,
|
||
|
value, TRUE,
|
||
|
error);
|
||
|
}
|
||
|
|
||
|
if (*error)
|
||
|
pika_extension_clean (extension);
|
||
|
|
||
|
g_object_set (extension,
|
||
|
"running", TRUE,
|
||
|
NULL);
|
||
|
|
||
|
return (*error == NULL);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
pika_extension_stop (PikaExtension *extension)
|
||
|
{
|
||
|
pika_extension_clean (extension);
|
||
|
g_object_set (extension,
|
||
|
"running", FALSE,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_brush_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->brush_paths;
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_dynamics_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->dynamics_paths;
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_mypaint_brush_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->mypaint_brush_paths;
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_pattern_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->pattern_paths;
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_gradient_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->gradient_paths;
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_palette_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->palette_paths;
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_tool_preset_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->tool_preset_paths;
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_splash_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->splash_paths;
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_theme_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->theme_paths;
|
||
|
}
|
||
|
|
||
|
GList *
|
||
|
pika_extension_get_plug_in_paths (PikaExtension *extension)
|
||
|
{
|
||
|
return extension->p->plug_in_paths;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @extension1: a #PikaExtension.
|
||
|
* @extension2: another #PikaExtension.
|
||
|
*
|
||
|
* Compare 2 extensions by their ID.
|
||
|
*
|
||
|
* Returns: 0 if the 2 extensions have the same ID (even though they may
|
||
|
* represent different versions of the same extension).
|
||
|
*/
|
||
|
gint
|
||
|
pika_extension_cmp (PikaExtension *extension1,
|
||
|
PikaExtension *extension2)
|
||
|
{
|
||
|
g_return_val_if_fail (PIKA_IS_EXTENSION (extension1), -1);
|
||
|
g_return_val_if_fail (PIKA_IS_EXTENSION (extension2), -1);
|
||
|
|
||
|
return g_strcmp0 (pika_object_get_name (extension1),
|
||
|
pika_object_get_name (extension2));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @extension: a #PikaExtension.
|
||
|
* @id: an extension ID (reverse-DNS scheme)
|
||
|
*
|
||
|
* Compare the extension ID with @id.
|
||
|
*
|
||
|
* Returns: 0 if @extension have @id as appstream ID.
|
||
|
*/
|
||
|
gint
|
||
|
pika_extension_id_cmp (PikaExtension *extension,
|
||
|
const gchar *id)
|
||
|
{
|
||
|
return g_strcmp0 (pika_object_get_name (extension), id);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
pika_extension_clean (PikaExtension *extension)
|
||
|
{
|
||
|
g_list_free_full (extension->p->brush_paths, g_object_unref);
|
||
|
extension->p->brush_paths = NULL;
|
||
|
g_list_free_full (extension->p->dynamics_paths, g_object_unref);
|
||
|
extension->p->dynamics_paths = NULL;
|
||
|
g_list_free_full (extension->p->mypaint_brush_paths, g_object_unref);
|
||
|
extension->p->brush_paths = NULL;
|
||
|
g_list_free_full (extension->p->pattern_paths, g_object_unref);
|
||
|
extension->p->pattern_paths = NULL;
|
||
|
g_list_free_full (extension->p->gradient_paths, g_object_unref);
|
||
|
extension->p->gradient_paths = NULL;
|
||
|
g_list_free_full (extension->p->palette_paths, g_object_unref);
|
||
|
extension->p->palette_paths = NULL;
|
||
|
g_list_free_full (extension->p->tool_preset_paths, g_object_unref);
|
||
|
extension->p->tool_preset_paths = NULL;
|
||
|
g_list_free_full (extension->p->plug_in_paths, g_object_unref);
|
||
|
extension->p->plug_in_paths = NULL;
|
||
|
g_list_free_full (extension->p->splash_paths, g_object_unref);
|
||
|
extension->p->splash_paths = NULL;
|
||
|
g_list_free_full (extension->p->theme_paths, g_object_unref);
|
||
|
extension->p->theme_paths = NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_extension_file_cmp:
|
||
|
* @a:
|
||
|
* @b:
|
||
|
*
|
||
|
* A small g_file_equal() wrapper using GCompareFunc signature.
|
||
|
*/
|
||
|
static gint
|
||
|
pika_extension_file_cmp (GFile *a,
|
||
|
GFile *b)
|
||
|
{
|
||
|
return g_file_equal (a, b) ? 0 : 1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* pika_extension_validate_paths:
|
||
|
* @extension: the #PikaExtension
|
||
|
* @path: A list of directories separated by ':'.
|
||
|
* @error:
|
||
|
*
|
||
|
* Very similar to pika_path_parse() except that we don't use
|
||
|
* G_SEARCHPATH_SEPARATOR as path separator, because it must not be
|
||
|
* os-dependent.
|
||
|
* Also we only allow relative path which are children of the main
|
||
|
* extension directory (we do not allow extensions to list external
|
||
|
* folders).
|
||
|
*
|
||
|
* Returns: A #GList of #GFile as listed in @path.
|
||
|
**/
|
||
|
static GList *
|
||
|
pika_extension_validate_paths (PikaExtension *extension,
|
||
|
const gchar *paths,
|
||
|
gboolean as_directories,
|
||
|
GError **error)
|
||
|
{
|
||
|
gchar **patharray;
|
||
|
GList *list = NULL;
|
||
|
gint i;
|
||
|
|
||
|
g_return_val_if_fail (error && *error == NULL, FALSE);
|
||
|
|
||
|
if (!paths || ! (*paths))
|
||
|
return NULL;
|
||
|
|
||
|
patharray = g_strsplit (paths, ":", 0);
|
||
|
|
||
|
for (i = 0; patharray[i]; i++)
|
||
|
{
|
||
|
/* Note: appstream-glib is supposed to return everything as UTF-8,
|
||
|
* so we should not have to bother about this. */
|
||
|
gchar *path;
|
||
|
GFile *file;
|
||
|
GFile *ext_dir;
|
||
|
GFile *parent;
|
||
|
GFile *child;
|
||
|
gboolean is_subpath = FALSE;
|
||
|
gint max_depth = 10;
|
||
|
|
||
|
if (g_path_is_absolute (patharray[i]))
|
||
|
{
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_BAD_PATH,
|
||
|
_("'%s' is not a relative path."),
|
||
|
patharray[i]);
|
||
|
break;
|
||
|
}
|
||
|
path = g_build_filename (extension->p->path, patharray[i], NULL);
|
||
|
file = g_file_new_for_path (path);
|
||
|
g_free (path);
|
||
|
|
||
|
ext_dir = g_file_new_for_path (extension->p->path);
|
||
|
|
||
|
/* Even with relative paths, it is easy to trick the system
|
||
|
* and leak out of the extension. So check actual kinship.
|
||
|
*/
|
||
|
child = g_object_ref (file);
|
||
|
while (max_depth > 0 && (parent = g_file_get_parent (child)))
|
||
|
{
|
||
|
if (g_file_equal (parent, ext_dir))
|
||
|
{
|
||
|
is_subpath = TRUE;
|
||
|
g_object_unref (parent);
|
||
|
break;
|
||
|
}
|
||
|
g_object_unref (child);
|
||
|
child = parent;
|
||
|
/* Avoid unfinite looping. */
|
||
|
max_depth--;
|
||
|
}
|
||
|
g_object_unref (child);
|
||
|
g_object_unref (ext_dir);
|
||
|
|
||
|
if (! is_subpath)
|
||
|
{
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_BAD_PATH,
|
||
|
_("'%s' is not a child of the extension."),
|
||
|
patharray[i]);
|
||
|
g_object_unref (file);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (as_directories)
|
||
|
{
|
||
|
if (g_file_query_file_type (file,
|
||
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
|
NULL) != G_FILE_TYPE_DIRECTORY)
|
||
|
{
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_BAD_PATH,
|
||
|
_("'%s' is not a directory."),
|
||
|
patharray[i]);
|
||
|
g_object_unref (file);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (g_file_query_file_type (file,
|
||
|
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||
|
NULL) != G_FILE_TYPE_REGULAR)
|
||
|
{
|
||
|
*error = g_error_new (PIKA_EXTENSION_ERROR,
|
||
|
PIKA_EXTENSION_BAD_PATH,
|
||
|
_("'%s' is not a valid file."),
|
||
|
patharray[i]);
|
||
|
g_object_unref (file);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
g_return_val_if_fail (path != NULL, NULL);
|
||
|
if (g_list_find_custom (list, file, (GCompareFunc) pika_extension_file_cmp))
|
||
|
{
|
||
|
/* Silently ignore duplicate paths. */
|
||
|
g_object_unref (file);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
list = g_list_prepend (list, file);
|
||
|
}
|
||
|
|
||
|
g_strfreev (patharray);
|
||
|
list = g_list_reverse (list);
|
||
|
|
||
|
return list;
|
||
|
}
|