/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimptagcache.c * Copyright (C) 2008 Aurimas Juška * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include "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, "\n"); g_string_append (buf, "\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 \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, " %s\n", tag_string); g_free (tag_string); } } g_string_append (buf, " \n"); } g_string_append (buf, "\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"); }