summaryrefslogtreecommitdiffstats
path: root/app/core/gimptagcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/core/gimptagcache.c')
-rw-r--r--app/core/gimptagcache.c646
1 files changed, 646 insertions, 0 deletions
diff --git a/app/core/gimptagcache.c b/app/core/gimptagcache.c
new file mode 100644
index 0000000..a0e4534
--- /dev/null
+++ b/app/core/gimptagcache.c
@@ -0,0 +1,646 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptagcache.c
+ * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.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 <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "config/gimpxmlparser.h"
+
+#include "gimp-memsize.h"
+#include "gimpcontext.h"
+#include "gimpdata.h"
+#include "gimplist.h"
+#include "gimptag.h"
+#include "gimptagcache.h"
+#include "gimptagged.h"
+
+#include "gimp-intl.h"
+
+
+#define GIMP_TAG_CACHE_FILE "tags.xml"
+
+/* #define DEBUG_GIMP_TAG_CACHE 1 */
+
+
+enum
+{
+ PROP_0,
+ PROP_GIMP
+};
+
+
+typedef struct
+{
+ GQuark identifier;
+ GQuark checksum;
+ GList *tags;
+ guint referenced : 1;
+} GimpTagCacheRecord;
+
+typedef struct
+{
+ GArray *records;
+ GimpTagCacheRecord current_record;
+} GimpTagCacheParseData;
+
+struct _GimpTagCachePrivate
+{
+ GArray *records;
+ GList *containers;
+};
+
+
+static void gimp_tag_cache_finalize (GObject *object);
+
+static gint64 gimp_tag_cache_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+static void gimp_tag_cache_object_initialize (GimpTagged *tagged,
+ GimpTagCache *cache);
+static void gimp_tag_cache_add_object (GimpTagCache *cache,
+ GimpTagged *tagged);
+
+static void gimp_tag_cache_load_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void gimp_tag_cache_load_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+static void gimp_tag_cache_load_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+static void gimp_tag_cache_load_error (GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data);
+static const gchar * gimp_tag_cache_attribute_name_to_value
+ (const gchar **attribute_names,
+ const gchar **attribute_values,
+ const gchar *name);
+
+static GQuark gimp_tag_cache_get_error_domain (void);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpTagCache, gimp_tag_cache, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_tag_cache_parent_class
+
+
+static void
+gimp_tag_cache_class_init (GimpTagCacheClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_tag_cache_finalize;
+
+ gimp_object_class->get_memsize = gimp_tag_cache_get_memsize;
+}
+
+static void
+gimp_tag_cache_init (GimpTagCache *cache)
+{
+ cache->priv = gimp_tag_cache_get_instance_private (cache);
+
+ cache->priv->records = g_array_new (FALSE, FALSE,
+ sizeof (GimpTagCacheRecord));
+ cache->priv->containers = NULL;
+}
+
+static void
+gimp_tag_cache_finalize (GObject *object)
+{
+ GimpTagCache *cache = GIMP_TAG_CACHE (object);
+
+ if (cache->priv->records)
+ {
+ gint i;
+
+ for (i = 0; i < cache->priv->records->len; i++)
+ {
+ GimpTagCacheRecord *rec = &g_array_index (cache->priv->records,
+ GimpTagCacheRecord, i);
+
+ g_list_free_full (rec->tags, (GDestroyNotify) g_object_unref);
+ }
+
+ g_array_free (cache->priv->records, TRUE);
+ cache->priv->records = NULL;
+ }
+
+ if (cache->priv->containers)
+ {
+ g_list_free (cache->priv->containers);
+ cache->priv->containers = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_tag_cache_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpTagCache *cache = GIMP_TAG_CACHE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_list_get_memsize (cache->priv->containers, 0);
+ memsize += cache->priv->records->len * sizeof (GimpTagCacheRecord);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+/**
+ * gimp_tag_cache_new:
+ *
+ * Return value: creates new GimpTagCache object.
+ **/
+GimpTagCache *
+gimp_tag_cache_new (void)
+{
+ return g_object_new (GIMP_TYPE_TAG_CACHE, NULL);
+}
+
+static void
+gimp_tag_cache_container_add_callback (GimpTagCache *cache,
+ GimpTagged *tagged,
+ GimpContainer *not_used)
+{
+ gimp_tag_cache_add_object (cache, tagged);
+}
+
+/**
+ * gimp_tag_cache_add_container:
+ * @cache: a GimpTagCache object.
+ * @container: container containing GimpTagged objects.
+ *
+ * Adds container of GimpTagged objects to tag cache. Before calling this
+ * function tag cache must be loaded using gimp_tag_cache_load(). When tag
+ * cache is saved to file, tags are collected from objects in priv->containers.
+ **/
+void
+gimp_tag_cache_add_container (GimpTagCache *cache,
+ GimpContainer *container)
+{
+ g_return_if_fail (GIMP_IS_TAG_CACHE (cache));
+ g_return_if_fail (GIMP_IS_CONTAINER (container));
+
+ cache->priv->containers = g_list_append (cache->priv->containers, container);
+ gimp_container_foreach (container, (GFunc) gimp_tag_cache_object_initialize,
+ cache);
+
+ g_signal_connect_swapped (container, "add",
+ G_CALLBACK (gimp_tag_cache_container_add_callback),
+ cache);
+}
+
+static void
+gimp_tag_cache_add_object (GimpTagCache *cache,
+ GimpTagged *tagged)
+{
+ gchar *identifier;
+ GQuark identifier_quark = 0;
+ gchar *checksum;
+ GQuark checksum_quark = 0;
+ GList *list;
+ gint i;
+
+ identifier = gimp_tagged_get_identifier (tagged);
+
+ if (identifier)
+ {
+ identifier_quark = g_quark_try_string (identifier);
+ g_free (identifier);
+ }
+
+ if (identifier_quark)
+ {
+ for (i = 0; i < cache->priv->records->len; i++)
+ {
+ GimpTagCacheRecord *rec = &g_array_index (cache->priv->records,
+ GimpTagCacheRecord, i);
+
+ if (rec->identifier == identifier_quark)
+ {
+ for (list = rec->tags; list; list = g_list_next (list))
+ {
+ gimp_tagged_add_tag (tagged, GIMP_TAG (list->data));
+ }
+
+ rec->referenced = TRUE;
+ return;
+ }
+ }
+ }
+
+ checksum = gimp_tagged_get_checksum (tagged);
+
+ if (checksum)
+ {
+ checksum_quark = g_quark_try_string (checksum);
+ g_free (checksum);
+ }
+
+ if (checksum_quark)
+ {
+ for (i = 0; i < cache->priv->records->len; i++)
+ {
+ GimpTagCacheRecord *rec = &g_array_index (cache->priv->records,
+ GimpTagCacheRecord, i);
+
+ if (rec->checksum == checksum_quark)
+ {
+#if DEBUG_GIMP_TAG_CACHE
+ g_printerr ("remapping identifier: %s ==> %s\n",
+ rec->identifier ? g_quark_to_string (rec->identifier) : "(NULL)",
+ identifier_quark ? g_quark_to_string (identifier_quark) : "(NULL)");
+#endif
+
+ rec->identifier = identifier_quark;
+
+ for (list = rec->tags; list; list = g_list_next (list))
+ {
+ gimp_tagged_add_tag (tagged, GIMP_TAG (list->data));
+ }
+
+ rec->referenced = TRUE;
+ return;
+ }
+ }
+ }
+
+}
+
+static void
+gimp_tag_cache_object_initialize (GimpTagged *tagged,
+ GimpTagCache *cache)
+{
+ gimp_tag_cache_add_object (cache, tagged);
+}
+
+static void
+gimp_tag_cache_tagged_to_cache_record_foreach (GimpTagged *tagged,
+ GList **cache_records)
+{
+ gchar *identifier = gimp_tagged_get_identifier (tagged);
+
+ if (identifier)
+ {
+ GimpTagCacheRecord *cache_rec = g_new (GimpTagCacheRecord, 1);
+ gchar *checksum;
+
+ checksum = gimp_tagged_get_checksum (tagged);
+
+ cache_rec->identifier = g_quark_from_string (identifier);
+ cache_rec->checksum = g_quark_from_string (checksum);
+ cache_rec->tags = g_list_copy (gimp_tagged_get_tags (tagged));
+
+ g_free (checksum);
+
+ *cache_records = g_list_prepend (*cache_records, cache_rec);
+ }
+
+ g_free (identifier);
+}
+
+/**
+ * gimp_tag_cache_save:
+ * @cache: a GimpTagCache object.
+ *
+ * Saves tag cache to cache file.
+ **/
+void
+gimp_tag_cache_save (GimpTagCache *cache)
+{
+ GString *buf;
+ GList *saved_records;
+ GList *iterator;
+ GFile *file;
+ GOutputStream *output;
+ GError *error = NULL;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_TAG_CACHE (cache));
+
+ saved_records = NULL;
+ for (i = 0; i < cache->priv->records->len; i++)
+ {
+ GimpTagCacheRecord *current_record = &g_array_index (cache->priv->records,
+ GimpTagCacheRecord, i);
+
+ if (! current_record->referenced && current_record->tags)
+ {
+ /* keep tagged objects which have tags assigned
+ * but were not loaded.
+ */
+ GimpTagCacheRecord *record_copy = g_new (GimpTagCacheRecord, 1);
+
+ record_copy->identifier = current_record->identifier;
+ record_copy->checksum = current_record->checksum;
+ record_copy->tags = g_list_copy (current_record->tags);
+
+ saved_records = g_list_prepend (saved_records, record_copy);
+ }
+ }
+
+ for (iterator = cache->priv->containers;
+ iterator;
+ iterator = g_list_next (iterator))
+ {
+ gimp_container_foreach (GIMP_CONTAINER (iterator->data),
+ (GFunc) gimp_tag_cache_tagged_to_cache_record_foreach,
+ &saved_records);
+ }
+
+ saved_records = g_list_reverse (saved_records);
+
+ buf = g_string_new ("");
+ g_string_append (buf, "<?xml version='1.0' encoding='UTF-8'?>\n");
+ g_string_append (buf, "<tags>\n");
+
+ for (iterator = saved_records; iterator; iterator = g_list_next (iterator))
+ {
+ GimpTagCacheRecord *cache_rec = iterator->data;
+ GList *tag_iterator;
+ gchar *identifier_string;
+ gchar *tag_string;
+
+ identifier_string = g_markup_escape_text (g_quark_to_string (cache_rec->identifier), -1);
+ g_string_append_printf (buf, "\n <resource identifier=\"%s\" checksum=\"%s\">\n",
+ identifier_string,
+ g_quark_to_string (cache_rec->checksum));
+ g_free (identifier_string);
+
+ for (tag_iterator = cache_rec->tags;
+ tag_iterator;
+ tag_iterator = g_list_next (tag_iterator))
+ {
+ GimpTag *tag = GIMP_TAG (tag_iterator->data);
+
+ if (! gimp_tag_get_internal (tag))
+ {
+ tag_string = g_markup_escape_text (gimp_tag_get_name (tag), -1);
+ g_string_append_printf (buf, " <tag>%s</tag>\n", tag_string);
+ g_free (tag_string);
+ }
+ }
+
+ g_string_append (buf, " </resource>\n");
+ }
+
+ g_string_append (buf, "</tags>\n");
+
+ file = gimp_directory_file (GIMP_TAG_CACHE_FILE, NULL);
+
+ output = G_OUTPUT_STREAM (g_file_replace (file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, &error));
+ if (! output)
+ {
+ g_printerr ("%s\n", error->message);
+ }
+ else if (! g_output_stream_write_all (output, buf->str, buf->len,
+ NULL, NULL, &error))
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_printerr (_("Error writing '%s': %s\n"),
+ gimp_file_get_utf8_name (file), error->message);
+
+ /* Cancel the overwrite initiated by g_file_replace(). */
+ g_cancellable_cancel (cancellable);
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+ }
+ else if (! g_output_stream_close (output, NULL, &error))
+ {
+ g_printerr (_("Error closing '%s': %s\n"),
+ gimp_file_get_utf8_name (file), error->message);
+ }
+
+ if (output)
+ g_object_unref (output);
+
+ g_clear_error (&error);
+ g_object_unref (file);
+ g_string_free (buf, TRUE);
+
+ for (iterator = saved_records;
+ iterator;
+ iterator = g_list_next (iterator))
+ {
+ GimpTagCacheRecord *cache_rec = iterator->data;
+
+ g_list_free (cache_rec->tags);
+ g_free (cache_rec);
+ }
+
+ g_list_free (saved_records);
+}
+
+/**
+ * gimp_tag_cache_load:
+ * @cache: a GimpTagCache object.
+ *
+ * Loads tag cache from file.
+ **/
+void
+gimp_tag_cache_load (GimpTagCache *cache)
+{
+ GFile *file;
+ GMarkupParser markup_parser;
+ GimpXmlParser *xml_parser;
+ GimpTagCacheParseData parse_data;
+ GError *error = NULL;
+
+ g_return_if_fail (GIMP_IS_TAG_CACHE (cache));
+
+ /* clear any previous priv->records */
+ cache->priv->records = g_array_set_size (cache->priv->records, 0);
+
+ parse_data.records = g_array_new (FALSE, FALSE, sizeof (GimpTagCacheRecord));
+ memset (&parse_data.current_record, 0, sizeof (GimpTagCacheRecord));
+
+ markup_parser.start_element = gimp_tag_cache_load_start_element;
+ markup_parser.end_element = gimp_tag_cache_load_end_element;
+ markup_parser.text = gimp_tag_cache_load_text;
+ markup_parser.passthrough = NULL;
+ markup_parser.error = gimp_tag_cache_load_error;
+
+ xml_parser = gimp_xml_parser_new (&markup_parser, &parse_data);
+
+ file = gimp_directory_file (GIMP_TAG_CACHE_FILE, NULL);
+
+ if (gimp_xml_parser_parse_gfile (xml_parser, file, &error))
+ {
+ cache->priv->records = g_array_append_vals (cache->priv->records,
+ parse_data.records->data,
+ parse_data.records->len);
+ }
+ else
+ {
+ g_printerr ("Failed to parse tag cache: %s\n",
+ error ? error->message : "WTF unknown error");
+ }
+
+ g_object_unref (file);
+ gimp_xml_parser_free (xml_parser);
+ g_array_free (parse_data.records, TRUE);
+}
+
+static void
+gimp_tag_cache_load_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ GimpTagCacheParseData *parse_data = user_data;
+
+ if (! strcmp (element_name, "resource"))
+ {
+ const gchar *identifier;
+ const gchar *checksum;
+
+ identifier = gimp_tag_cache_attribute_name_to_value (attribute_names,
+ attribute_values,
+ "identifier");
+ checksum = gimp_tag_cache_attribute_name_to_value (attribute_names,
+ attribute_values,
+ "checksum");
+
+ if (! identifier)
+ {
+ g_set_error (error,
+ gimp_tag_cache_get_error_domain (),
+ 1001,
+ "Resource tag does not contain required attribute identifier.");
+ return;
+ }
+
+ memset (&parse_data->current_record, 0, sizeof (GimpTagCacheRecord));
+
+ parse_data->current_record.identifier = g_quark_from_string (identifier);
+ parse_data->current_record.checksum = g_quark_from_string (checksum);
+ }
+}
+
+static void
+gimp_tag_cache_load_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ GimpTagCacheParseData *parse_data = user_data;
+
+ if (strcmp (element_name, "resource") == 0)
+ {
+ parse_data->records = g_array_append_val (parse_data->records,
+ parse_data->current_record);
+ memset (&parse_data->current_record, 0, sizeof (GimpTagCacheRecord));
+ }
+}
+
+static void
+gimp_tag_cache_load_text (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ GimpTagCacheParseData *parse_data = user_data;
+ const gchar *current_element;
+ gchar buffer[2048];
+ GimpTag *tag;
+
+ current_element = g_markup_parse_context_get_element (context);
+
+ if (g_strcmp0 (current_element, "tag") == 0)
+ {
+ if (text_len >= sizeof (buffer))
+ {
+ g_set_error (error, gimp_tag_cache_get_error_domain (), 1002,
+ "Tag value is too long.");
+ return;
+ }
+
+ memcpy (buffer, text, text_len);
+ buffer[text_len] = '\0';
+
+ tag = gimp_tag_new (buffer);
+ if (tag)
+ {
+ parse_data->current_record.tags = g_list_append (parse_data->current_record.tags,
+ tag);
+ }
+ else
+ {
+ g_warning ("dropping invalid tag '%s' from '%s'\n", buffer,
+ g_quark_to_string (parse_data->current_record.identifier));
+ }
+ }
+}
+
+static void
+gimp_tag_cache_load_error (GMarkupParseContext *context,
+ GError *error,
+ gpointer user_data)
+{
+ g_printerr ("Tag cache parse error: %s\n", error->message);
+}
+
+static const gchar*
+gimp_tag_cache_attribute_name_to_value (const gchar **attribute_names,
+ const gchar **attribute_values,
+ const gchar *name)
+{
+ while (*attribute_names)
+ {
+ if (! strcmp (*attribute_names, name))
+ {
+ return *attribute_values;
+ }
+
+ attribute_names++;
+ attribute_values++;
+ }
+
+ return NULL;
+}
+
+static GQuark
+gimp_tag_cache_get_error_domain (void)
+{
+ return g_quark_from_static_string ("gimp-tag-cache-error-quark");
+}