specified by GimpDataLoaderEntry structs. Remove the same code from
GimpDataFactory and make it an abstract base class that only serves as
an interface for actual implementations. Also move around some stuff
in GimpDataFactory and remove virtual functions that were a bad idea
in the first place.
(cherry picked from commit 73da7c9a54)
467 lines
16 KiB
C
467 lines
16 KiB
C
/* GIMP - The GNU Image Manipulation Program
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* gimpdataloaderfactory.c
|
|
* Copyright (C) 2001-2018 Michael Natterer <mitch@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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
#include <gegl.h>
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
|
|
|
#include "core-types.h"
|
|
|
|
#include "gimp.h"
|
|
#include "gimp-utils.h"
|
|
#include "gimpcontainer.h"
|
|
#include "gimpdata.h"
|
|
#include "gimpdataloaderfactory.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
/* Data files that have this string in their path are considered
|
|
* obsolete and are only kept around for backwards compatibility
|
|
*/
|
|
#define GIMP_OBSOLETE_DATA_DIR_NAME "gimp-obsolete-files"
|
|
|
|
|
|
struct _GimpDataLoaderFactoryPrivate
|
|
{
|
|
const GimpDataLoaderEntry *loader_entries;
|
|
gint n_loader_entries;
|
|
};
|
|
|
|
#define GET_PRIVATE(obj) (((GimpDataLoaderFactory *) (obj))->priv)
|
|
|
|
|
|
static void gimp_data_loader_factory_data_init (GimpDataFactory *factory,
|
|
GimpContext *context);
|
|
static void gimp_data_loader_factory_data_refresh (GimpDataFactory *factory,
|
|
GimpContext *context);
|
|
|
|
static void gimp_data_loader_factory_load (GimpDataFactory *factory,
|
|
GimpContext *context,
|
|
GHashTable *cache);
|
|
static void gimp_data_loader_factory_load_directory (GimpDataFactory *factory,
|
|
GimpContext *context,
|
|
GHashTable *cache,
|
|
gboolean dir_writable,
|
|
GFile *directory,
|
|
GFile *top_directory);
|
|
static void gimp_data_loader_factory_load_data (GimpDataFactory *factory,
|
|
GimpContext *context,
|
|
GHashTable *cache,
|
|
gboolean dir_writable,
|
|
GFile *file,
|
|
GFileInfo *info,
|
|
GFile *top_directory);
|
|
|
|
|
|
G_DEFINE_TYPE (GimpDataLoaderFactory, gimp_data_loader_factory,
|
|
GIMP_TYPE_DATA_FACTORY)
|
|
|
|
#define parent_class gimp_data_loader_factory_parent_class
|
|
|
|
|
|
static void
|
|
gimp_data_loader_factory_class_init (GimpDataLoaderFactoryClass *klass)
|
|
{
|
|
GimpDataFactoryClass *factory_class = GIMP_DATA_FACTORY_CLASS (klass);
|
|
|
|
factory_class->data_init = gimp_data_loader_factory_data_init;
|
|
factory_class->data_refresh = gimp_data_loader_factory_data_refresh;
|
|
|
|
g_type_class_add_private (klass, sizeof (GimpDataLoaderFactoryPrivate));
|
|
}
|
|
|
|
static void
|
|
gimp_data_loader_factory_init (GimpDataLoaderFactory *factory)
|
|
{
|
|
factory->priv = G_TYPE_INSTANCE_GET_PRIVATE (factory,
|
|
GIMP_TYPE_DATA_LOADER_FACTORY,
|
|
GimpDataLoaderFactoryPrivate);
|
|
}
|
|
|
|
static void
|
|
gimp_data_loader_factory_data_init (GimpDataFactory *factory,
|
|
GimpContext *context)
|
|
{
|
|
gimp_data_loader_factory_load (factory, context, NULL);
|
|
}
|
|
|
|
static void
|
|
gimp_data_loader_factory_refresh_cache_add (GimpDataFactory *factory,
|
|
GimpData *data,
|
|
gpointer user_data)
|
|
{
|
|
GFile *file = gimp_data_get_file (data);
|
|
|
|
if (file)
|
|
{
|
|
GimpContainer *container = gimp_data_factory_get_container (factory);
|
|
GHashTable *cache = user_data;
|
|
GList *list;
|
|
|
|
g_object_ref (data);
|
|
|
|
gimp_container_remove (container, GIMP_OBJECT (data));
|
|
|
|
list = g_hash_table_lookup (cache, file);
|
|
list = g_list_prepend (list, data);
|
|
|
|
g_hash_table_insert (cache, file, list);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gimp_data_loader_factory_refresh_cache_remove (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
GList *list;
|
|
|
|
for (list = value; list; list = list->next)
|
|
g_object_unref (list->data);
|
|
|
|
g_list_free (value);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gimp_data_loader_factory_data_refresh (GimpDataFactory *factory,
|
|
GimpContext *context)
|
|
{
|
|
GimpContainer *container = gimp_data_factory_get_container (factory);
|
|
GHashTable *cache;
|
|
|
|
gimp_container_freeze (container);
|
|
|
|
/* First, save all dirty data objects */
|
|
gimp_data_factory_data_save (factory);
|
|
|
|
cache = g_hash_table_new (g_file_hash, (GEqualFunc) g_file_equal);
|
|
|
|
gimp_data_factory_data_foreach (factory, TRUE,
|
|
gimp_data_loader_factory_refresh_cache_add,
|
|
cache);
|
|
|
|
/* Now the cache contains a GFile => list-of-objects mapping of
|
|
* the old objects. So we should now traverse the directory and for
|
|
* each file load it only if its mtime is newer.
|
|
*
|
|
* Once a file was added, it is removed from the cache, so the only
|
|
* objects remaining there will be those that are not present on
|
|
* the disk (that have to be destroyed)
|
|
*/
|
|
gimp_data_loader_factory_load (factory, context, cache);
|
|
|
|
/* Now all the data is loaded. Free what remains in the cache */
|
|
g_hash_table_foreach_remove (cache,
|
|
gimp_data_loader_factory_refresh_cache_remove,
|
|
NULL);
|
|
|
|
g_hash_table_destroy (cache);
|
|
|
|
gimp_container_thaw (container);
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
GimpDataFactory *
|
|
gimp_data_loader_factory_new (Gimp *gimp,
|
|
GType data_type,
|
|
const gchar *path_property_name,
|
|
const gchar *writable_property_name,
|
|
GimpDataNewFunc new_func,
|
|
GimpDataGetStandardFunc get_standard_func,
|
|
const GimpDataLoaderEntry *loader_entries,
|
|
gint n_loader_entries)
|
|
{
|
|
GimpDataLoaderFactory *factory;
|
|
|
|
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
|
|
g_return_val_if_fail (g_type_is_a (data_type, GIMP_TYPE_DATA), NULL);
|
|
g_return_val_if_fail (path_property_name != NULL, NULL);
|
|
g_return_val_if_fail (writable_property_name != NULL, NULL);
|
|
g_return_val_if_fail (loader_entries != NULL, NULL);
|
|
g_return_val_if_fail (n_loader_entries > 0, NULL);
|
|
|
|
factory = g_object_new (GIMP_TYPE_DATA_LOADER_FACTORY,
|
|
"gimp", gimp,
|
|
"data-type", data_type,
|
|
"path-property-name", path_property_name,
|
|
"writable-property-name", writable_property_name,
|
|
"new-func", new_func,
|
|
"get-standard-func", get_standard_func,
|
|
NULL);
|
|
|
|
factory->priv->loader_entries = loader_entries;
|
|
factory->priv->n_loader_entries = n_loader_entries;
|
|
|
|
return GIMP_DATA_FACTORY (factory);
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static void
|
|
gimp_data_loader_factory_load (GimpDataFactory *factory,
|
|
GimpContext *context,
|
|
GHashTable *cache)
|
|
{
|
|
GList *path;
|
|
GList *writable_path;
|
|
GList *list;
|
|
|
|
path = gimp_data_factory_get_data_path (factory);
|
|
writable_path = gimp_data_factory_get_data_path_writable (factory);
|
|
|
|
for (list = path; list; list = g_list_next (list))
|
|
{
|
|
gboolean dir_writable = FALSE;
|
|
|
|
if (g_list_find_custom (writable_path, list->data,
|
|
(GCompareFunc) gimp_file_compare))
|
|
dir_writable = TRUE;
|
|
|
|
gimp_data_loader_factory_load_directory (factory, context, cache,
|
|
dir_writable,
|
|
list->data,
|
|
list->data);
|
|
}
|
|
|
|
g_list_free_full (path, (GDestroyNotify) g_object_unref);
|
|
g_list_free_full (writable_path, (GDestroyNotify) g_object_unref);
|
|
}
|
|
|
|
static void
|
|
gimp_data_loader_factory_load_directory (GimpDataFactory *factory,
|
|
GimpContext *context,
|
|
GHashTable *cache,
|
|
gboolean dir_writable,
|
|
GFile *directory,
|
|
GFile *top_directory)
|
|
{
|
|
GFileEnumerator *enumerator;
|
|
|
|
enumerator = g_file_enumerate_children (directory,
|
|
G_FILE_ATTRIBUTE_STANDARD_NAME ","
|
|
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
|
|
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
|
|
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)))
|
|
{
|
|
GFileType file_type;
|
|
GFile *child;
|
|
|
|
if (g_file_info_get_is_hidden (info))
|
|
{
|
|
g_object_unref (info);
|
|
continue;
|
|
}
|
|
|
|
file_type = g_file_info_get_file_type (info);
|
|
child = g_file_enumerator_get_child (enumerator, info);
|
|
|
|
if (file_type == G_FILE_TYPE_DIRECTORY)
|
|
{
|
|
gimp_data_loader_factory_load_directory (factory, context, cache,
|
|
dir_writable,
|
|
child,
|
|
top_directory);
|
|
}
|
|
else if (file_type == G_FILE_TYPE_REGULAR)
|
|
{
|
|
gimp_data_loader_factory_load_data (factory, context, cache,
|
|
dir_writable,
|
|
child, info,
|
|
top_directory);
|
|
}
|
|
|
|
g_object_unref (child);
|
|
g_object_unref (info);
|
|
}
|
|
|
|
g_object_unref (enumerator);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_data_loader_factory_load_data (GimpDataFactory *factory,
|
|
GimpContext *context,
|
|
GHashTable *cache,
|
|
gboolean dir_writable,
|
|
GFile *file,
|
|
GFileInfo *info,
|
|
GFile *top_directory)
|
|
{
|
|
GimpDataLoaderFactoryPrivate *priv = GET_PRIVATE (factory);
|
|
const GimpDataLoaderEntry *loader = NULL;
|
|
GimpContainer *container;
|
|
GimpContainer *container_obsolete;
|
|
GList *data_list = NULL;
|
|
GInputStream *input;
|
|
guint64 mtime;
|
|
gint i;
|
|
GError *error = NULL;
|
|
|
|
for (i = 0; i < priv->n_loader_entries; i++)
|
|
{
|
|
loader = &priv->loader_entries[i];
|
|
|
|
/* a loder matches if its extension matches, or if it doesn't
|
|
* have an extension, which is the case for the fallback loader,
|
|
* which must be last in the loader array
|
|
*/
|
|
if (! loader->extension ||
|
|
gimp_file_has_extension (file, loader->extension))
|
|
{
|
|
goto insert;
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
insert:
|
|
container = gimp_data_factory_get_container (factory);
|
|
container_obsolete = gimp_data_factory_get_container_obsolete (factory);
|
|
|
|
mtime = g_file_info_get_attribute_uint64 (info,
|
|
G_FILE_ATTRIBUTE_TIME_MODIFIED);
|
|
|
|
if (cache)
|
|
{
|
|
GList *cached_data = g_hash_table_lookup (cache, file);
|
|
|
|
if (cached_data &&
|
|
gimp_data_get_mtime (cached_data->data) != 0 &&
|
|
gimp_data_get_mtime (cached_data->data) == mtime)
|
|
{
|
|
GList *list;
|
|
|
|
for (list = cached_data; list; list = g_list_next (list))
|
|
gimp_container_add (container, list->data);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
input = G_INPUT_STREAM (g_file_read (file, NULL, &error));
|
|
|
|
if (input)
|
|
{
|
|
GInputStream *buffered = g_buffered_input_stream_new (input);
|
|
|
|
data_list = loader->load_func (context, file, buffered, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_prefix_error (&error,
|
|
_("Error loading '%s': "),
|
|
gimp_file_get_utf8_name (file));
|
|
}
|
|
else if (! data_list)
|
|
{
|
|
g_set_error (&error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
|
_("Error loading '%s'"),
|
|
gimp_file_get_utf8_name (file));
|
|
}
|
|
|
|
g_object_unref (buffered);
|
|
g_object_unref (input);
|
|
}
|
|
else
|
|
{
|
|
g_prefix_error (&error,
|
|
_("Could not open '%s' for reading: "),
|
|
gimp_file_get_utf8_name (file));
|
|
}
|
|
|
|
if (G_LIKELY (data_list))
|
|
{
|
|
GList *list;
|
|
gchar *uri;
|
|
gboolean obsolete;
|
|
gboolean writable = FALSE;
|
|
gboolean deletable = FALSE;
|
|
|
|
uri = g_file_get_uri (file);
|
|
|
|
obsolete = (strstr (uri, GIMP_OBSOLETE_DATA_DIR_NAME) != 0);
|
|
|
|
g_free (uri);
|
|
|
|
/* obsolete files are immutable, don't check their writability */
|
|
if (! obsolete)
|
|
{
|
|
deletable = (g_list_length (data_list) == 1 && dir_writable);
|
|
writable = (deletable && loader->writable);
|
|
}
|
|
|
|
for (list = data_list; list; list = g_list_next (list))
|
|
{
|
|
GimpData *data = list->data;
|
|
|
|
gimp_data_set_file (data, file, writable, deletable);
|
|
gimp_data_set_mtime (data, mtime);
|
|
gimp_data_clean (data);
|
|
|
|
if (obsolete)
|
|
{
|
|
gimp_container_add (container_obsolete,
|
|
GIMP_OBJECT (data));
|
|
}
|
|
else
|
|
{
|
|
gimp_data_set_folder_tags (data, top_directory);
|
|
|
|
gimp_container_add (container,
|
|
GIMP_OBJECT (data));
|
|
}
|
|
|
|
g_object_unref (data);
|
|
}
|
|
|
|
g_list_free (data_list);
|
|
}
|
|
|
|
/* not else { ... } because loader->load_func() can return a list
|
|
* of data objects *and* an error message if loading failed after
|
|
* something was already loaded
|
|
*/
|
|
if (G_UNLIKELY (error))
|
|
{
|
|
gimp_message (gimp_data_factory_get_gimp (factory), NULL,
|
|
GIMP_MESSAGE_ERROR,
|
|
_("Failed to load data:\n\n%s"), error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
}
|