/* 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 * * pikadatafactory.c * Copyright (C) 2001-2018 Michael Natterer * * 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 #include "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "libpikaconfig/pikaconfig.h" #include "core-types.h" #include "pika.h" #include "pika-utils.h" #include "pikaasyncset.h" #include "pikacancelable.h" #include "pikacontext.h" #include "pikadata.h" #include "pikadatafactory.h" #include "pikalist.h" #include "pikauncancelablewaitable.h" #include "pikawaitable.h" #include "pika-intl.h" enum { PROP_0, PROP_PIKA, PROP_DATA_TYPE, PROP_PATH_PROPERTY_NAME, PROP_WRITABLE_PROPERTY_NAME, PROP_EXT_PROPERTY_NAME, PROP_NEW_FUNC, PROP_GET_STANDARD_FUNC, PROP_UNIQUE_NAMES }; struct _PikaDataFactoryPrivate { Pika *pika; GType data_type; PikaContainer *container; PikaContainer *container_obsolete; gboolean unique_names; gchar *path_property_name; gchar *writable_property_name; gchar *ext_property_name; PikaDataNewFunc data_new_func; PikaDataGetStandardFunc data_get_standard_func; PikaAsyncSet *async_set; }; #define GET_PRIVATE(obj) (((PikaDataFactory *) (obj))->priv) static void pika_data_factory_constructed (GObject *object); static void pika_data_factory_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_data_factory_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_data_factory_finalize (GObject *object); static gint64 pika_data_factory_get_memsize (PikaObject *object, gint64 *gui_size); static void pika_data_factory_real_data_save (PikaDataFactory *factory); static void pika_data_factory_real_data_cancel (PikaDataFactory *factory); static PikaData * pika_data_factory_real_data_duplicate (PikaDataFactory *factory, PikaData *data); static gboolean pika_data_factory_real_data_delete (PikaDataFactory *factory, PikaData *data, gboolean delete_from_disk, GError **error); static void pika_data_factory_path_notify (GObject *object, const GParamSpec *pspec, PikaDataFactory *factory); static GFile * pika_data_factory_get_save_dir (PikaDataFactory *factory, GError **error); G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (PikaDataFactory, pika_data_factory, PIKA_TYPE_OBJECT) #define parent_class pika_data_factory_parent_class static void pika_data_factory_class_init (PikaDataFactoryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); object_class->constructed = pika_data_factory_constructed; object_class->set_property = pika_data_factory_set_property; object_class->get_property = pika_data_factory_get_property; object_class->finalize = pika_data_factory_finalize; pika_object_class->get_memsize = pika_data_factory_get_memsize; klass->data_init = NULL; klass->data_refresh = NULL; klass->data_save = pika_data_factory_real_data_save; klass->data_cancel = pika_data_factory_real_data_cancel; klass->data_duplicate = pika_data_factory_real_data_duplicate; klass->data_delete = pika_data_factory_real_data_delete; 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_DATA_TYPE, g_param_spec_gtype ("data-type", NULL, NULL, PIKA_TYPE_DATA, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_PATH_PROPERTY_NAME, g_param_spec_string ("path-property-name", NULL, NULL, NULL, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_WRITABLE_PROPERTY_NAME, g_param_spec_string ("writable-property-name", NULL, NULL, NULL, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_EXT_PROPERTY_NAME, g_param_spec_string ("ext-property-name", NULL, NULL, NULL, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_NEW_FUNC, g_param_spec_pointer ("new-func", NULL, NULL, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_GET_STANDARD_FUNC, g_param_spec_pointer ("get-standard-func", NULL, NULL, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_UNIQUE_NAMES, g_param_spec_boolean ("unique-names", NULL, NULL, TRUE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void pika_data_factory_init (PikaDataFactory *factory) { factory->priv = pika_data_factory_get_instance_private (factory); factory->priv->async_set = pika_async_set_new (); } static void pika_data_factory_constructed (GObject *object) { PikaDataFactoryPrivate *priv = GET_PRIVATE (object); G_OBJECT_CLASS (parent_class)->constructed (object); pika_assert (PIKA_IS_PIKA (priv->pika)); pika_assert (g_type_is_a (priv->data_type, PIKA_TYPE_DATA)); pika_assert (PIKA_DATA_FACTORY_GET_CLASS (object)->data_init != NULL); pika_assert (PIKA_DATA_FACTORY_GET_CLASS (object)->data_refresh != NULL); /* Passing along the "unique names" property to the data container. */ priv->container = pika_list_new (priv->data_type, priv->unique_names); pika_list_set_sort_func (PIKA_LIST (priv->container), (GCompareFunc) pika_data_compare); priv->container_obsolete = pika_list_new (priv->data_type, TRUE); pika_list_set_sort_func (PIKA_LIST (priv->container_obsolete), (GCompareFunc) pika_data_compare); } static void pika_data_factory_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaDataFactoryPrivate *priv = GET_PRIVATE (object); switch (property_id) { case PROP_PIKA: priv->pika = g_value_get_object (value); /* don't ref */ break; case PROP_DATA_TYPE: priv->data_type = g_value_get_gtype (value); break; case PROP_PATH_PROPERTY_NAME: priv->path_property_name = g_value_dup_string (value); break; case PROP_WRITABLE_PROPERTY_NAME: priv->writable_property_name = g_value_dup_string (value); break; case PROP_EXT_PROPERTY_NAME: priv->ext_property_name = g_value_dup_string (value); break; case PROP_NEW_FUNC: priv->data_new_func = g_value_get_pointer (value); break; case PROP_GET_STANDARD_FUNC: priv->data_get_standard_func = g_value_get_pointer (value); break; case PROP_UNIQUE_NAMES: priv->unique_names = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_data_factory_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaDataFactoryPrivate *priv = GET_PRIVATE (object); switch (property_id) { case PROP_PIKA: g_value_set_object (value, priv->pika); break; case PROP_DATA_TYPE: g_value_set_gtype (value, priv->data_type); break; case PROP_PATH_PROPERTY_NAME: g_value_set_string (value, priv->path_property_name); break; case PROP_WRITABLE_PROPERTY_NAME: g_value_set_string (value, priv->writable_property_name); break; case PROP_EXT_PROPERTY_NAME: g_value_set_string (value, priv->ext_property_name); break; case PROP_NEW_FUNC: g_value_set_pointer (value, priv->data_new_func); break; case PROP_GET_STANDARD_FUNC: g_value_set_pointer (value, priv->data_get_standard_func); break; case PROP_UNIQUE_NAMES: g_value_set_boolean (value, priv->unique_names); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_data_factory_finalize (GObject *object) { PikaDataFactory *factory = PIKA_DATA_FACTORY (object); PikaDataFactoryPrivate *priv = GET_PRIVATE (object); if (priv->async_set) { pika_data_factory_data_cancel (factory); g_clear_object (&priv->async_set); } g_clear_object (&priv->container); g_clear_object (&priv->container_obsolete); g_clear_pointer (&priv->path_property_name, g_free); g_clear_pointer (&priv->writable_property_name, g_free); g_clear_pointer (&priv->ext_property_name, g_free); G_OBJECT_CLASS (parent_class)->finalize (object); } static gint64 pika_data_factory_get_memsize (PikaObject *object, gint64 *gui_size) { PikaDataFactoryPrivate *priv = GET_PRIVATE (object); gint64 memsize = 0; memsize += pika_object_get_memsize (PIKA_OBJECT (priv->container), gui_size); memsize += pika_object_get_memsize (PIKA_OBJECT (priv->container_obsolete), gui_size); return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static void pika_data_factory_real_data_save (PikaDataFactory *factory) { PikaDataFactoryPrivate *priv = GET_PRIVATE (factory); GList *dirty = NULL; GList *list; GFile *writable_dir; GError *error = NULL; for (list = PIKA_LIST (priv->container)->queue->head; list; list = g_list_next (list)) { PikaData *data = list->data; if (pika_data_is_dirty (data) && pika_data_is_writable (data)) { dirty = g_list_prepend (dirty, data); } } if (! dirty) return; writable_dir = pika_data_factory_get_save_dir (factory, &error); if (! writable_dir) { pika_message (priv->pika, NULL, PIKA_MESSAGE_ERROR, _("Failed to save data:\n\n%s"), error->message); g_clear_error (&error); g_list_free (dirty); return; } for (list = dirty; list; list = g_list_next (list)) { PikaData *data = list->data; GError *error = NULL; if (! pika_data_get_file (data)) pika_data_create_filename (data, writable_dir); if (factory->priv->pika->be_verbose) { GFile *file = pika_data_get_file (data); if (file) g_print ("Writing dirty data '%s'\n", pika_file_get_utf8_name (file)); } if (! pika_data_save (data, &error)) { /* check if there actually was an error (no error * means the data class does not implement save) */ if (error) { pika_message (priv->pika, NULL, PIKA_MESSAGE_ERROR, _("Failed to save data:\n\n%s"), error->message); g_clear_error (&error); } } } g_object_unref (writable_dir); g_list_free (dirty); } static void pika_data_factory_real_data_cancel (PikaDataFactory *factory) { PikaDataFactoryPrivate *priv = GET_PRIVATE (factory); pika_cancelable_cancel (PIKA_CANCELABLE (priv->async_set)); pika_waitable_wait (PIKA_WAITABLE (priv->async_set)); } static PikaData * pika_data_factory_real_data_duplicate (PikaDataFactory *factory, PikaData *data) { PikaDataFactoryPrivate *priv = GET_PRIVATE (factory); PikaData *new_data; new_data = pika_data_duplicate (data); if (new_data) { const gchar *name = pika_object_get_name (data); gchar *ext; gint copy_len; gint number; gchar *new_name; ext = strrchr (name, '#'); copy_len = strlen (_("copy")); if ((strlen (name) >= copy_len && strcmp (&name[strlen (name) - copy_len], _("copy")) == 0) || (ext && (number = atoi (ext + 1)) > 0 && ((gint) (log10 (number) + 1)) == strlen (ext + 1))) { /* don't have redundant "copy"s */ new_name = g_strdup (name); } else { new_name = g_strdup_printf (_("%s copy"), name); } pika_object_take_name (PIKA_OBJECT (new_data), new_name); pika_container_add (priv->container, PIKA_OBJECT (new_data)); g_object_unref (new_data); } return new_data; } static gboolean pika_data_factory_real_data_delete (PikaDataFactory *factory, PikaData *data, gboolean delete_from_disk, GError **error) { if (delete_from_disk && pika_data_get_file (data)) return pika_data_delete_from_disk (data, error); return TRUE; } /* public functions */ void pika_data_factory_data_init (PikaDataFactory *factory, PikaContext *context, gboolean no_data) { PikaDataFactoryPrivate *priv = GET_PRIVATE (factory); gchar *signal_name; g_return_if_fail (PIKA_IS_DATA_FACTORY (factory)); g_return_if_fail (PIKA_IS_CONTEXT (context)); /* Always freeze() and thaw() the container around initialization, * even if no_data, the thaw() will implicitly make PikaContext * create the standard data that serves as fallback. */ pika_container_freeze (priv->container); if (! no_data) { if (priv->pika->be_verbose) { const gchar *name = pika_object_get_name (factory); g_print ("Loading '%s' data\n", name ? name : "???"); } PIKA_DATA_FACTORY_GET_CLASS (factory)->data_init (factory, context); } pika_container_thaw (priv->container); signal_name = g_strdup_printf ("notify::%s", priv->path_property_name); g_signal_connect_object (priv->pika->config, signal_name, G_CALLBACK (pika_data_factory_path_notify), factory, 0); g_free (signal_name); signal_name = g_strdup_printf ("notify::%s", priv->ext_property_name); g_signal_connect_object (priv->pika->extension_manager, signal_name, G_CALLBACK (pika_data_factory_path_notify), factory, 0); g_free (signal_name); } static void pika_data_factory_clean_cb (PikaDataFactory *factory, PikaData *data, gpointer user_data) { if (pika_data_is_dirty (data)) pika_data_clean (data); } void pika_data_factory_data_clean (PikaDataFactory *factory) { g_return_if_fail (PIKA_IS_DATA_FACTORY (factory)); pika_data_factory_data_foreach (factory, TRUE, pika_data_factory_clean_cb, NULL); } void pika_data_factory_data_refresh (PikaDataFactory *factory, PikaContext *context) { g_return_if_fail (PIKA_IS_DATA_FACTORY (factory)); g_return_if_fail (PIKA_IS_CONTEXT (context)); PIKA_DATA_FACTORY_GET_CLASS (factory)->data_refresh (factory, context); } void pika_data_factory_data_save (PikaDataFactory *factory) { g_return_if_fail (PIKA_IS_DATA_FACTORY (factory)); if (! pika_container_is_empty (factory->priv->container)) PIKA_DATA_FACTORY_GET_CLASS (factory)->data_save (factory); } static void pika_data_factory_data_free_foreach (PikaDataFactory *factory, PikaData *data, gpointer user_data) { pika_container_remove (factory->priv->container, PIKA_OBJECT (data)); } void pika_data_factory_data_free (PikaDataFactory *factory) { g_return_if_fail (PIKA_IS_DATA_FACTORY (factory)); pika_data_factory_data_cancel (factory); if (! pika_container_is_empty (factory->priv->container)) { pika_container_freeze (factory->priv->container); pika_data_factory_data_foreach (factory, TRUE, pika_data_factory_data_free_foreach, NULL); pika_container_thaw (factory->priv->container); } } PikaAsyncSet * pika_data_factory_get_async_set (PikaDataFactory *factory) { g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); return factory->priv->async_set; } gboolean pika_data_factory_data_wait (PikaDataFactory *factory) { PikaDataFactoryPrivate *priv; PikaWaitable *waitable; g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), FALSE); priv = GET_PRIVATE (factory); /* don't allow cancellation for now */ waitable = pika_uncancelable_waitable_new (PIKA_WAITABLE (priv->async_set)); pika_wait (priv->pika, waitable, _("Loading fonts (this may take a while...)")); g_object_unref (waitable); return TRUE; } void pika_data_factory_data_cancel (PikaDataFactory *factory) { g_return_if_fail (PIKA_IS_DATA_FACTORY (factory)); PIKA_DATA_FACTORY_GET_CLASS (factory)->data_cancel (factory); } gboolean pika_data_factory_has_data_new_func (PikaDataFactory *factory) { g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), FALSE); return factory->priv->data_new_func != NULL; } PikaData * pika_data_factory_data_new (PikaDataFactory *factory, PikaContext *context, const gchar *name) { PikaDataFactoryPrivate *priv; g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (*name != '\0', NULL); priv = GET_PRIVATE (factory); if (priv->data_new_func) { PikaData *data = priv->data_new_func (context, name); if (data) { pika_container_add (priv->container, PIKA_OBJECT (data)); g_object_unref (data); return data; } g_warning ("%s: PikaDataFactory::data_new_func() returned NULL", G_STRFUNC); } return NULL; } PikaData * pika_data_factory_data_get_standard (PikaDataFactory *factory, PikaContext *context) { g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); if (factory->priv->data_get_standard_func) return factory->priv->data_get_standard_func (context); return NULL; } PikaData * pika_data_factory_data_duplicate (PikaDataFactory *factory, PikaData *data) { g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); g_return_val_if_fail (PIKA_IS_DATA (data), NULL); return PIKA_DATA_FACTORY_GET_CLASS (factory)->data_duplicate (factory, data); } gboolean pika_data_factory_data_delete (PikaDataFactory *factory, PikaData *data, gboolean delete_from_disk, GError **error) { PikaDataFactoryPrivate *priv; gboolean retval = TRUE; g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), FALSE); g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); priv = GET_PRIVATE (factory); if (pika_container_have (priv->container, PIKA_OBJECT (data))) { g_object_ref (data); pika_container_remove (priv->container, PIKA_OBJECT (data)); retval = PIKA_DATA_FACTORY_GET_CLASS (factory)->data_delete (factory, data, delete_from_disk, error); g_object_unref (data); } return retval; } gboolean pika_data_factory_data_save_single (PikaDataFactory *factory, PikaData *data, GError **error) { g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), FALSE); g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (! pika_data_is_dirty (data)) return TRUE; if (! pika_data_get_file (data)) { GFile *writable_dir; GError *my_error = NULL; writable_dir = pika_data_factory_get_save_dir (factory, &my_error); if (! writable_dir) { g_set_error (error, PIKA_DATA_ERROR, 0, _("Failed to save data:\n\n%s"), my_error->message); g_clear_error (&my_error); return FALSE; } pika_data_create_filename (data, writable_dir); g_object_unref (writable_dir); } if (! pika_data_is_writable (data)) return FALSE; if (factory->priv->pika->be_verbose) { GFile *file = pika_data_get_file (data); if (file) g_print ("Writing dirty data '%s'\n", pika_file_get_utf8_name (file)); } if (! pika_data_save (data, error)) { /* check if there actually was an error (no error * means the data class does not implement save) */ if (! error) g_set_error (error, PIKA_DATA_ERROR, 0, _("Failed to save data:\n\n%s"), "Data class does not implement saving"); return FALSE; } return TRUE; } void pika_data_factory_data_foreach (PikaDataFactory *factory, gboolean skip_internal, PikaDataForeachFunc callback, gpointer user_data) { GList *list; g_return_if_fail (PIKA_IS_DATA_FACTORY (factory)); g_return_if_fail (callback != NULL); list = PIKA_LIST (factory->priv->container)->queue->head; while (list) { GList *next = g_list_next (list); if (! (skip_internal && pika_data_is_internal (list->data))) callback (factory, list->data, user_data); list = next; } } Pika * pika_data_factory_get_pika (PikaDataFactory *factory) { g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); return factory->priv->pika; } GType pika_data_factory_get_data_type (PikaDataFactory *factory) { g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), G_TYPE_NONE); return pika_container_get_children_type (factory->priv->container); } PikaContainer * pika_data_factory_get_container (PikaDataFactory *factory) { g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); return factory->priv->container; } PikaContainer * pika_data_factory_get_container_obsolete (PikaDataFactory *factory) { g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); return factory->priv->container_obsolete; } GList * pika_data_factory_get_data_path (PikaDataFactory *factory) { PikaDataFactoryPrivate *priv = GET_PRIVATE (factory); gchar *path = NULL; GList *list = NULL; g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); g_object_get (priv->pika->config, priv->path_property_name, &path, NULL); if (path) { list = pika_config_path_expand_to_files (path, NULL); g_free (path); } return list; } GList * pika_data_factory_get_data_path_writable (PikaDataFactory *factory) { PikaDataFactoryPrivate *priv = GET_PRIVATE (factory); gchar *path = NULL; GList *list = NULL; g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); g_object_get (priv->pika->config, priv->writable_property_name, &path, NULL); if (path) { list = pika_config_path_expand_to_files (path, NULL); g_free (path); } return list; } const GList * pika_data_factory_get_data_path_ext (PikaDataFactory *factory) { PikaDataFactoryPrivate *priv = GET_PRIVATE (factory); GList *list = NULL; g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL); g_object_get (priv->pika->extension_manager, priv->ext_property_name, &list, NULL); return list; } /* private functions */ static void pika_data_factory_path_notify (GObject *object, const GParamSpec *pspec, PikaDataFactory *factory) { PikaDataFactoryPrivate *priv = GET_PRIVATE (factory); pika_set_busy (priv->pika); pika_data_factory_data_refresh (factory, pika_get_user_context (priv->pika)); pika_unset_busy (priv->pika); } static GFile * pika_data_factory_get_save_dir (PikaDataFactory *factory, GError **error) { GList *path; GList *writable_path; GFile *writable_dir = NULL; path = pika_data_factory_get_data_path (factory); writable_path = pika_data_factory_get_data_path_writable (factory); if (writable_path) { GList *list; gboolean found_any = FALSE; for (list = writable_path; list; list = g_list_next (list)) { GList *found = g_list_find_custom (path, list->data, (GCompareFunc) pika_file_compare); if (found) { GFile *dir = found->data; found_any = TRUE; if (g_file_query_file_type (dir, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY) { /* error out only if this is the last chance */ if (! list->next) { g_set_error (error, PIKA_DATA_ERROR, 0, _("You have a writable data folder " "configured (%s), but this folder does " "not exist. Please create the folder or " "fix your configuration in the " "Preferences dialog's 'Folders' section."), pika_file_get_utf8_name (dir)); } } else { writable_dir = g_object_ref (dir); break; } } } if (! writable_dir && ! found_any) { g_set_error (error, PIKA_DATA_ERROR, 0, _("You have a writable data folder configured, but this " "folder is not part of your data search path. You " "probably edited the pikarc file manually, " "please fix it in the Preferences dialog's 'Folders' " "section.")); } } else { g_set_error (error, PIKA_DATA_ERROR, 0, _("You don't have any writable data folder configured.")); } g_list_free_full (path, (GDestroyNotify) g_object_unref); g_list_free_full (writable_path, (GDestroyNotify) g_object_unref); return writable_dir; }