/* 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 * * pikadata.c * Copyright (C) 2001 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 "libpikabase/pikabase.h" #include "core-types.h" #include "pika-memsize.h" #include "pikadata.h" #include "pikaidtable.h" #include "pikaimage.h" #include "pikatag.h" #include "pikatagged.h" #include "pika-intl.h" enum { DIRTY, LAST_SIGNAL }; enum { PROP_0, PROP_ID, PROP_FILE, PROP_IMAGE, PROP_WRITABLE, PROP_DELETABLE, PROP_MIME_TYPE }; struct _PikaDataPrivate { gint ID; GFile *file; PikaImage *image; GQuark mime_type; guint writable : 1; guint deletable : 1; guint dirty : 1; guint internal : 1; gint freeze_count; gint64 mtime; /* Identifies the collection this PikaData belongs to. * Used when there is not a filename associated with the object. */ gchar *collection; GList *tags; }; #define PIKA_DATA_GET_PRIVATE(obj) (((PikaData *) (obj))->priv) static void pika_data_tagged_iface_init (PikaTaggedInterface *iface); static void pika_data_constructed (GObject *object); static void pika_data_finalize (GObject *object); static void pika_data_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_data_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_data_name_changed (PikaObject *object); static gint64 pika_data_get_memsize (PikaObject *object, gint64 *gui_size); static gboolean pika_data_is_name_editable (PikaViewable *viewable); static void pika_data_real_dirty (PikaData *data); static PikaData * pika_data_real_duplicate (PikaData *data); static gint pika_data_real_compare (PikaData *data1, PikaData *data2); static gboolean pika_data_add_tag (PikaTagged *tagged, PikaTag *tag); static gboolean pika_data_remove_tag (PikaTagged *tagged, PikaTag *tag); static GList * pika_data_get_tags (PikaTagged *tagged); static gchar * pika_data_get_identifier (PikaTagged *tagged); static gchar * pika_data_get_checksum (PikaTagged *tagged); static gchar * pika_data_get_collection (PikaData *data); G_DEFINE_TYPE_WITH_CODE (PikaData, pika_data, PIKA_TYPE_RESOURCE, G_ADD_PRIVATE (PikaData) G_IMPLEMENT_INTERFACE (PIKA_TYPE_TAGGED, pika_data_tagged_iface_init)) #define parent_class pika_data_parent_class static guint data_signals[LAST_SIGNAL] = { 0 }; static PikaIdTable *data_id_table = NULL; static void pika_data_class_init (PikaDataClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); parent_class = g_type_class_peek_parent (klass); data_signals[DIRTY] = g_signal_new ("dirty", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaDataClass, dirty), NULL, NULL, NULL, G_TYPE_NONE, 0); object_class->constructed = pika_data_constructed; object_class->finalize = pika_data_finalize; object_class->set_property = pika_data_set_property; object_class->get_property = pika_data_get_property; pika_object_class->name_changed = pika_data_name_changed; pika_object_class->get_memsize = pika_data_get_memsize; viewable_class->name_editable = TRUE; viewable_class->is_name_editable = pika_data_is_name_editable; klass->dirty = pika_data_real_dirty; klass->save = NULL; klass->get_extension = NULL; klass->copy = NULL; klass->duplicate = pika_data_real_duplicate; klass->compare = pika_data_real_compare; g_object_class_install_property (object_class, PROP_ID, g_param_spec_int ("id", NULL, NULL, 0, G_MAXINT, 0, PIKA_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_FILE, g_param_spec_object ("file", NULL, NULL, G_TYPE_FILE, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_IMAGE, g_param_spec_object ("image", NULL, NULL, PIKA_TYPE_IMAGE, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_WRITABLE, g_param_spec_boolean ("writable", NULL, NULL, FALSE, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_DELETABLE, g_param_spec_boolean ("deletable", NULL, NULL, FALSE, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_MIME_TYPE, g_param_spec_string ("mime-type", NULL, NULL, NULL, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); data_id_table = pika_id_table_new (); } static void pika_data_tagged_iface_init (PikaTaggedInterface *iface) { iface->add_tag = pika_data_add_tag; iface->remove_tag = pika_data_remove_tag; iface->get_tags = pika_data_get_tags; iface->get_identifier = pika_data_get_identifier; iface->get_checksum = pika_data_get_checksum; } static void pika_data_init (PikaData *data) { PikaDataPrivate *private = pika_data_get_instance_private (data); data->priv = private; private->ID = pika_id_table_insert (data_id_table, data); private->writable = TRUE; private->deletable = TRUE; private->dirty = TRUE; /* freeze the data object during construction */ pika_data_freeze (data); } static void pika_data_constructed (GObject *object) { PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (object); G_OBJECT_CLASS (parent_class)->constructed (object); if (! PIKA_DATA_GET_CLASS (object)->save) private->writable = FALSE; pika_data_thaw (PIKA_DATA (object)); } static void pika_data_finalize (GObject *object) { PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (object); pika_id_table_remove (data_id_table, private->ID); g_clear_object (&private->file); if (private->tags) { g_list_free_full (private->tags, (GDestroyNotify) g_object_unref); private->tags = NULL; } g_clear_pointer (&private->collection, g_free); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_data_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaData *data = PIKA_DATA (object); PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (data); switch (property_id) { case PROP_FILE: pika_data_set_file (data, g_value_get_object (value), private->writable, private->deletable); break; case PROP_IMAGE: pika_data_set_image (data, g_value_get_object (value), private->writable, private->deletable); break; case PROP_WRITABLE: private->writable = g_value_get_boolean (value); break; case PROP_DELETABLE: private->deletable = g_value_get_boolean (value); break; case PROP_MIME_TYPE: if (g_value_get_string (value)) private->mime_type = g_quark_from_string (g_value_get_string (value)); else private->mime_type = 0; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_data_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (object); switch (property_id) { case PROP_ID: g_value_set_int (value, private->ID); break; case PROP_FILE: g_value_set_object (value, private->file); break; case PROP_IMAGE: g_value_set_object (value, private->image); break; case PROP_WRITABLE: g_value_set_boolean (value, private->writable); break; case PROP_DELETABLE: g_value_set_boolean (value, private->deletable); break; case PROP_MIME_TYPE: g_value_set_string (value, g_quark_to_string (private->mime_type)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_data_name_changed (PikaObject *object) { PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (object); private->dirty = TRUE; if (PIKA_OBJECT_CLASS (parent_class)->name_changed) PIKA_OBJECT_CLASS (parent_class)->name_changed (object); } static gint64 pika_data_get_memsize (PikaObject *object, gint64 *gui_size) { PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (object); gint64 memsize = 0; memsize += pika_g_object_get_memsize (G_OBJECT (private->file)); return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static gboolean pika_data_is_name_editable (PikaViewable *viewable) { return pika_data_is_writable (PIKA_DATA (viewable)) && ! pika_data_is_internal (PIKA_DATA (viewable)); } static void pika_data_real_dirty (PikaData *data) { pika_viewable_invalidate_preview (PIKA_VIEWABLE (data)); /* Emit the "name-changed" to signal general dirtiness, our name * changed implementation will also set the "dirty" flag to TRUE. */ pika_object_name_changed (PIKA_OBJECT (data)); } static PikaData * pika_data_real_duplicate (PikaData *data) { if (PIKA_DATA_GET_CLASS (data)->copy) { PikaData *new = g_object_new (G_OBJECT_TYPE (data), NULL); pika_data_copy (new, data); return new; } return NULL; } static gint pika_data_real_compare (PikaData *data1, PikaData *data2) { PikaDataPrivate *private1 = PIKA_DATA_GET_PRIVATE (data1); PikaDataPrivate *private2 = PIKA_DATA_GET_PRIVATE (data2); /* move the internal objects (like the FG -> BG) gradient) to the top */ if (private1->internal != private2->internal) return private1->internal ? -1 : 1; /* keep user-deletable objects above system resource files */ if (private1->deletable != private2->deletable) return private1->deletable ? -1 : 1; if (g_strcmp0 (private1->collection, private2->collection) != 0) return g_strcmp0 (private1->collection, private2->collection); return pika_object_name_collate ((PikaObject *) data1, (PikaObject *) data2); } static gboolean pika_data_add_tag (PikaTagged *tagged, PikaTag *tag) { PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (tagged); GList *list; for (list = private->tags; list; list = g_list_next (list)) { PikaTag *this = PIKA_TAG (list->data); if (pika_tag_equals (tag, this)) return FALSE; } private->tags = g_list_prepend (private->tags, g_object_ref (tag)); return TRUE; } static gboolean pika_data_remove_tag (PikaTagged *tagged, PikaTag *tag) { PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (tagged); GList *list; for (list = private->tags; list; list = g_list_next (list)) { PikaTag *this = PIKA_TAG (list->data); if (pika_tag_equals (tag, this)) { private->tags = g_list_delete_link (private->tags, list); g_object_unref (this); return TRUE; } } return FALSE; } static GList * pika_data_get_tags (PikaTagged *tagged) { PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (tagged); return private->tags; } static gchar * pika_data_get_identifier (PikaTagged *tagged) { PikaData *data = PIKA_DATA (tagged); PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (data); gchar *identifier = NULL; gchar *collection = NULL; g_return_val_if_fail (private->internal || private->file != NULL || private->image != NULL, NULL); collection = pika_data_get_collection (data); /* The identifier is guaranteed to be unique because we use 2 directory * separators between the collection and the data name. Since the collection * is either a controlled internal name or built from g_file_get_path(), which * is guaranteed to be a canonical path, we know it won't contain 2 * separators. Therefore it should be impossible to construct a file path able * to create duplicate identifiers. * The last point is obviously that it should not be possible to have * duplicate data names in a single collection. So every identifier should be * unique. */ identifier = g_strdup_printf ("%s:%s%s%s%s", private->internal ? "internal" : "external", collection, G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S, pika_object_get_name (PIKA_OBJECT (data))); g_free (collection); return identifier; } static gchar * pika_data_get_checksum (PikaTagged *tagged) { return NULL; } /* * A data collection name is either generated from the file path, or set when * marking a data as internal. * Several data objects may belong to a same collection. A very common example * of this in fonts are collections of fonts (e.g. TrueType Collection .TTC * files). */ static gchar * pika_data_get_collection (PikaData *data) { PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (data); gchar *collection = NULL; g_return_val_if_fail (private->internal || private->file != NULL || private->image != NULL, NULL); if (private->file) { const gchar *data_dir = pika_data_directory (); const gchar *pika_dir = pika_directory (); gchar *path = g_file_get_path (private->file); gchar *tmp; if (g_str_has_prefix (path, data_dir)) { tmp = g_strconcat ("${pika_data_dir}", path + strlen (data_dir), NULL); collection = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL); g_free (tmp); } else if (g_str_has_prefix (path, pika_dir)) { tmp = g_strconcat ("${pika_dir}", path + strlen (pika_dir), NULL); collection = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL); g_free (tmp); } else if (g_str_has_prefix (path, MYPAINT_BRUSHES_DIR)) { tmp = g_strconcat ("${mypaint_brushes_dir}", path + strlen (MYPAINT_BRUSHES_DIR), NULL); collection = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL); g_free (tmp); } else { collection = g_filename_to_utf8 (path, -1, NULL, NULL, NULL); } if (! collection) { g_printerr ("%s: failed to convert '%s' to utf8.\n", G_STRFUNC, path); collection = g_strdup (path); } g_free (path); } else if (private->image) { collection = g_strdup_printf ("[image-id-%d]", pika_image_get_id (private->image)); } else if (private->internal) { collection = g_strdup (private->collection); } return collection; } /* public functions */ gint pika_data_get_id (PikaData *data) { g_return_val_if_fail (PIKA_IS_DATA (data), -1); return PIKA_DATA_GET_PRIVATE (data)->ID; } PikaData * pika_data_get_by_id (gint data_id) { return (PikaData *) pika_id_table_lookup (data_id_table, data_id); } /** * pika_data_save: * @data: object whose contents are to be saved. * @error: return location for errors or %NULL * * Save the object. If the object is marked as "internal", nothing * happens. Otherwise, it is saved to disk, using the file name set * by pika_data_set_file(). If the save is successful, the object is * marked as not dirty. If not, an error message is returned using * the @error argument. * * Returns: %TRUE if the object is internal or the save is successful. **/ gboolean pika_data_save (PikaData *data, GError **error) { PikaDataPrivate *private; gboolean success = FALSE; g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); private = PIKA_DATA_GET_PRIVATE (data); g_return_val_if_fail (private->writable == TRUE, FALSE); if (private->internal || private->image != NULL) { private->dirty = FALSE; return TRUE; } g_return_val_if_fail (G_IS_FILE (private->file), FALSE); if (PIKA_DATA_GET_CLASS (data)->save) { GOutputStream *output; output = G_OUTPUT_STREAM (g_file_replace (private->file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (output) { success = PIKA_DATA_GET_CLASS (data)->save (data, output, error); if (success) { if (! g_output_stream_close (output, NULL, error)) { g_prefix_error (error, _("Error saving '%s': "), pika_file_get_utf8_name (private->file)); success = FALSE; } } else { GCancellable *cancellable = g_cancellable_new (); g_cancellable_cancel (cancellable); if (error && *error) { g_prefix_error (error, _("Error saving '%s': "), pika_file_get_utf8_name (private->file)); } else { g_set_error (error, PIKA_DATA_ERROR, PIKA_DATA_ERROR_WRITE, _("Error saving '%s'"), pika_file_get_utf8_name (private->file)); } g_output_stream_close (output, cancellable, NULL); g_object_unref (cancellable); } g_object_unref (output); } } if (success) { GFileInfo *info = g_file_query_info (private->file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (info) { private->mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); g_object_unref (info); } private->dirty = FALSE; } return success; } /** * pika_data_dirty: * @data: a #PikaData object. * * Marks @data as dirty. Unless the object is frozen, this causes * its preview to be invalidated, and emits a "dirty" signal. If the * object is frozen, the function has no effect. **/ void pika_data_dirty (PikaData *data) { PikaDataPrivate *private; g_return_if_fail (PIKA_IS_DATA (data)); private = PIKA_DATA_GET_PRIVATE (data); if (private->freeze_count == 0) g_signal_emit (data, data_signals[DIRTY], 0); } void pika_data_clean (PikaData *data) { PikaDataPrivate *private; g_return_if_fail (PIKA_IS_DATA (data)); private = PIKA_DATA_GET_PRIVATE (data); private->dirty = FALSE; } gboolean pika_data_is_dirty (PikaData *data) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); private = PIKA_DATA_GET_PRIVATE (data); return private->dirty; } /** * pika_data_freeze: * @data: a #PikaData object. * * Increments the freeze count for the object. A positive freeze count * prevents the object from being treated as dirty. Any call to this * function must be followed eventually by a call to pika_data_thaw(). **/ void pika_data_freeze (PikaData *data) { PikaDataPrivate *private; g_return_if_fail (PIKA_IS_DATA (data)); private = PIKA_DATA_GET_PRIVATE (data); private->freeze_count++; } /** * pika_data_thaw: * @data: a #PikaData object. * * Decrements the freeze count for the object. If the freeze count * drops to zero, the object is marked as dirty, and the "dirty" * signal is emitted. It is an error to call this function without * having previously called pika_data_freeze(). **/ void pika_data_thaw (PikaData *data) { PikaDataPrivate *private; g_return_if_fail (PIKA_IS_DATA (data)); private = PIKA_DATA_GET_PRIVATE (data); g_return_if_fail (private->freeze_count > 0); private->freeze_count--; if (private->freeze_count == 0) pika_data_dirty (data); } gboolean pika_data_is_frozen (PikaData *data) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); private = PIKA_DATA_GET_PRIVATE (data); return private->freeze_count > 0; } /** * pika_data_delete_from_disk: * @data: a #PikaData object. * @error: return location for errors or %NULL * * Deletes the object from disk. If the object is marked as "internal", * nothing happens. Otherwise, if the file exists whose name has been * set by pika_data_set_file(), it is deleted. Obviously this is * a potentially dangerous function, which should be used with care. * * Returns: %TRUE if the object is internal to Gimp, or the deletion is * successful. **/ gboolean pika_data_delete_from_disk (PikaData *data, GError **error) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); private = PIKA_DATA_GET_PRIVATE (data); g_return_val_if_fail (private->file != NULL, FALSE); g_return_val_if_fail (private->deletable == TRUE, FALSE); if (private->internal) return TRUE; return g_file_delete (private->file, NULL, error); } const gchar * pika_data_get_extension (PikaData *data) { g_return_val_if_fail (PIKA_IS_DATA (data), NULL); if (PIKA_DATA_GET_CLASS (data)->get_extension) return PIKA_DATA_GET_CLASS (data)->get_extension (data); return NULL; } /** * pika_data_set_file: * @data: A #PikaData object * @file: File to assign to @data. * @writable: %TRUE if we want to be able to write to this file. * @deletable: %TRUE if we want to be able to delete this file. * * This function assigns a file to @data, and sets some flags * according to the properties of the file. If @writable is %TRUE, * and the user has permission to write or overwrite the requested * file name, and a "save" method exists for @data's object type, then * @data is marked as writable. **/ void pika_data_set_file (PikaData *data, GFile *file, gboolean writable, gboolean deletable) { PikaDataPrivate *private; gchar *path; g_return_if_fail (PIKA_IS_DATA (data)); g_return_if_fail (G_IS_FILE (file)); path = g_file_get_path (file); g_return_if_fail (path != NULL); g_return_if_fail (g_path_is_absolute (path)); g_free (path); private = PIKA_DATA_GET_PRIVATE (data); if (private->internal) return; g_return_if_fail (private->image == NULL); g_set_object (&private->file, file); private->writable = FALSE; private->deletable = FALSE; /* if the data is supposed to be writable or deletable, * still check if it really is */ if (writable || deletable) { GFileInfo *info; if (g_file_query_exists (private->file, NULL)) /* check if it exists */ { info = g_file_query_info (private->file, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, G_FILE_QUERY_INFO_NONE, NULL, NULL); /* and we can write it */ if (info) { if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) { private->writable = writable ? TRUE : FALSE; private->deletable = deletable ? TRUE : FALSE; } g_object_unref (info); } } else /* OR it doesn't exist */ { GFile *parent = g_file_get_parent (private->file); info = g_file_query_info (parent, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, G_FILE_QUERY_INFO_NONE, NULL, NULL); /* and we can write to its parent directory */ if (info) { if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) { private->writable = writable ? TRUE : FALSE; private->deletable = deletable ? TRUE : FALSE; } g_object_unref (info); } g_object_unref (parent); } /* if we can't save, we are not writable */ if (! PIKA_DATA_GET_CLASS (data)->save) private->writable = FALSE; } } GFile * pika_data_get_file (PikaData *data) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), NULL); private = PIKA_DATA_GET_PRIVATE (data); return private->file; } /** * pika_data_set_image: * @data: A #PikaData object * @image: Image to assign to @data. * @writable: %TRUE if we want to be able to write to this file. * @deletable: %TRUE if we want to be able to delete this file. * * This function assigns an image to @data. This can only be done if no file has * been assigned (a non-internal data can be attached either to a file or to an * image). **/ void pika_data_set_image (PikaData *data, PikaImage *image, gboolean writable, gboolean deletable) { PikaDataPrivate *private; g_return_if_fail (PIKA_IS_DATA (data)); g_return_if_fail (PIKA_IS_IMAGE (image)); private = PIKA_DATA_GET_PRIVATE (data); if (private->internal) return; g_return_if_fail (private->file == NULL); g_set_object (&private->image, image); private->writable = writable ? TRUE : FALSE; private->deletable = deletable ? TRUE : FALSE; } PikaImage * pika_data_get_image (PikaData *data) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), NULL); private = PIKA_DATA_GET_PRIVATE (data); return private->image; } /** * pika_data_create_filename: * @data: a #Pikadata object. * @dest_dir: directory in which to create a file name. * * This function creates a unique file name to be used for saving * a representation of @data in the directory @dest_dir. If the * user does not have write permission in @dest_dir, then @data * is marked as "not writable", so you should check on this before * assuming that @data can be saved. **/ void pika_data_create_filename (PikaData *data, GFile *dest_dir) { PikaDataPrivate *private; gchar *safename; gchar *basename; GFile *file; gint i; gint unum = 1; GError *error = NULL; g_return_if_fail (PIKA_IS_DATA (data)); g_return_if_fail (G_IS_FILE (dest_dir)); private = PIKA_DATA_GET_PRIVATE (data); if (private->internal) return; safename = g_strstrip (g_strdup (pika_object_get_name (data))); if (safename[0] == '.') safename[0] = '-'; for (i = 0; safename[i]; i++) if (strchr ("\\/*?\"`'<>{}|\n\t ;:$^&", safename[i])) safename[i] = '-'; basename = g_strconcat (safename, pika_data_get_extension (data), NULL); file = g_file_get_child_for_display_name (dest_dir, basename, &error); g_free (basename); if (! file) { g_warning ("pika_data_create_filename:\n" "g_file_get_child_for_display_name() failed for '%s': %s", pika_object_get_name (data), error->message); g_clear_error (&error); g_free (safename); return; } while (g_file_query_exists (file, NULL)) { g_object_unref (file); basename = g_strdup_printf ("%s-%d%s", safename, unum++, pika_data_get_extension (data)); file = g_file_get_child_for_display_name (dest_dir, basename, NULL); g_free (basename); } g_free (safename); pika_data_set_file (data, file, TRUE, TRUE); g_object_unref (file); } static const gchar *tag_blacklist[] = { "brushes", "dynamics", "patterns", "palettes", "gradients", "tool-presets" }; /** * pika_data_set_folder_tags: * @data: a #Pikadata object. * @top_directory: the top directory of the currently processed data * hierarchy. * * Sets tags based on all folder names below top_directory. So if the * data's filename is e.g. * /home/foo/.config/PIKA/X.Y/brushes/Flowers/Roses/rose.gbr, it will * add "Flowers" and "Roses" tags. * * if the top directory (as passed, or as derived from the data's * filename) does not end with one of the default data directory names * (brushes, patterns etc), its name will be added as tag too. **/ void pika_data_set_folder_tags (PikaData *data, GFile *top_directory) { PikaDataPrivate *private; gchar *tmp; gchar *dirname; gchar *top_path; g_return_if_fail (PIKA_IS_DATA (data)); g_return_if_fail (G_IS_FILE (top_directory)); private = PIKA_DATA_GET_PRIVATE (data); if (private->internal) return; g_return_if_fail (private->file != NULL); tmp = g_file_get_path (private->file); dirname = g_path_get_dirname (tmp); g_free (tmp); top_path = g_file_get_path (top_directory); g_return_if_fail (g_str_has_prefix (dirname, top_path)); /* walk up the hierarchy and set each folder on the way as tag, * except the top_directory */ while (strcmp (dirname, top_path)) { gchar *basename = g_path_get_basename (dirname); PikaTag *tag = pika_tag_new (basename); pika_tag_set_internal (tag, TRUE); pika_tagged_add_tag (PIKA_TAGGED (data), tag); g_object_unref (tag); g_free (basename); tmp = g_path_get_dirname (dirname); g_free (dirname); dirname = tmp; } g_free (top_path); if (dirname) { gchar *basename = g_path_get_basename (dirname); gint i; for (i = 0; i < G_N_ELEMENTS (tag_blacklist); i++) { if (! strcmp (basename, tag_blacklist[i])) break; } if (i == G_N_ELEMENTS (tag_blacklist)) { PikaTag *tag = pika_tag_new (basename); pika_tag_set_internal (tag, TRUE); pika_tagged_add_tag (PIKA_TAGGED (data), tag); g_object_unref (tag); } g_free (basename); g_free (dirname); } } const gchar * pika_data_get_mime_type (PikaData *data) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), NULL); private = PIKA_DATA_GET_PRIVATE (data); return g_quark_to_string (private->mime_type); } gboolean pika_data_is_writable (PikaData *data) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); private = PIKA_DATA_GET_PRIVATE (data); return private->writable; } gboolean pika_data_is_deletable (PikaData *data) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); private = PIKA_DATA_GET_PRIVATE (data); return private->deletable; } void pika_data_set_mtime (PikaData *data, gint64 mtime) { PikaDataPrivate *private; g_return_if_fail (PIKA_IS_DATA (data)); private = PIKA_DATA_GET_PRIVATE (data); private->mtime = mtime; } gint64 pika_data_get_mtime (PikaData *data) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), 0); private = PIKA_DATA_GET_PRIVATE (data); return private->mtime; } gboolean pika_data_is_copyable (PikaData *data) { g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); return PIKA_DATA_GET_CLASS (data)->copy != NULL; } /** * pika_data_copy: * @data: a #PikaData object * @src_data: the #PikaData object to copy from * * Copies @src_data to @data. Only the object data is copied: the * name, file name, preview, etc. are not copied. **/ void pika_data_copy (PikaData *data, PikaData *src_data) { g_return_if_fail (PIKA_IS_DATA (data)); g_return_if_fail (PIKA_IS_DATA (src_data)); g_return_if_fail (PIKA_DATA_GET_CLASS (data)->copy != NULL); g_return_if_fail (PIKA_DATA_GET_CLASS (data)->copy == PIKA_DATA_GET_CLASS (src_data)->copy); if (data != src_data) PIKA_DATA_GET_CLASS (data)->copy (data, src_data); } gboolean pika_data_is_duplicatable (PikaData *data) { g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); if (PIKA_DATA_GET_CLASS (data)->duplicate == pika_data_real_duplicate) return pika_data_is_copyable (data); else return PIKA_DATA_GET_CLASS (data)->duplicate != NULL; } /** * pika_data_duplicate: * @data: a #PikaData object * * Creates a copy of @data, if possible. Only the object data is * copied: the newly created object is not automatically given an * object name, file name, preview, etc. * * Returns: (nullable) (transfer full): the newly created copy, or %NULL if * @data cannot be copied. **/ PikaData * pika_data_duplicate (PikaData *data) { g_return_val_if_fail (PIKA_IS_DATA (data), NULL); if (pika_data_is_duplicatable (data)) { PikaData *new = PIKA_DATA_GET_CLASS (data)->duplicate (data); PikaDataPrivate *private = PIKA_DATA_GET_PRIVATE (new); g_object_set (new, "name", NULL, "writable", PIKA_DATA_GET_CLASS (new)->save != NULL, "deletable", TRUE, NULL); g_clear_object (&private->file); return new; } return NULL; } /** * pika_data_make_internal: * @data: a #PikaData object. * @collection: internal collection title @data belongs to. * * Mark @data as "internal" to Gimp, which means that it will not be * saved to disk. Note that if you do this, later calls to * pika_data_save() and pika_data_delete_from_disk() will * automatically return successfully without giving any warning. * * The @collection shall be an untranslated globally unique string * that identifies the internal object collection across sessions. **/ void pika_data_make_internal (PikaData *data, const gchar *collection) { PikaDataPrivate *private; g_return_if_fail (PIKA_IS_DATA (data)); private = PIKA_DATA_GET_PRIVATE (data); g_clear_object (&private->file); g_free (private->collection); private->collection = g_strdup (collection); private->writable = FALSE; private->deletable = FALSE; private->internal = TRUE; } gboolean pika_data_is_internal (PikaData *data) { PikaDataPrivate *private; g_return_val_if_fail (PIKA_IS_DATA (data), FALSE); private = PIKA_DATA_GET_PRIVATE (data); return private->internal; } /** * pika_data_compare: * @data1: a #PikaData object. * @data2: another #PikaData object. * * Compares two data objects for use in sorting. Objects marked as * "internal" come first, then user-writable objects, then system data * files. In these three groups, the objects are sorted alphabetically * by name, using pika_object_name_collate(). * * Returns: -1 if @data1 compares before @data2, * 0 if they compare equal, * 1 if @data1 compares after @data2. **/ gint pika_data_compare (PikaData *data1, PikaData *data2) { g_return_val_if_fail (PIKA_IS_DATA (data1), 0); g_return_val_if_fail (PIKA_IS_DATA (data2), 0); g_return_val_if_fail (PIKA_DATA_GET_CLASS (data1)->compare == PIKA_DATA_GET_CLASS (data2)->compare, 0); return PIKA_DATA_GET_CLASS (data1)->compare (data1, data2); } /** * pika_data_identify: * @data: a #PikaData object. * @name: name of the #PikaData object. * @collection: text uniquely identifying the collection @data belongs to. * @is_internal: whether this is internal data. * * Determine whether (@name, @collection, @is_internal) uniquely identify @data. * * Returns: %TRUE if the triplet identifies @data, %FALSE otherwise. **/ gboolean pika_data_identify (PikaData *data, const gchar *name, const gchar *collection, gboolean is_internal) { gchar *current_collection = pika_data_get_collection (data); gboolean identified; identified = (is_internal == pika_data_is_internal (data) && g_strcmp0 (collection, current_collection) == 0 && g_strcmp0 (name, pika_object_get_name (PIKA_OBJECT (data))) == 0); g_free (current_collection); return identified; } /** * pika_data_get_identifiers: * @data: a #PikaData object. * @name: name of the #PikaData object. * @collection: text uniquely identifying the collection @data belongs to. * @is_internal: whether this is internal data. * * Generates a triplet of identifiers which, together, should uniquely identify * this @data. * @name will be the same value as pika_object_get_name() and @is_internal the * same value as returned by pika_data_is_internal(), except that it is not * enough because two data from different sources may end up having the same * name. Nevertheless all data names within a single collection of data are * unique. @collection therefore identifies the source collection. And these 3 * identifiers together are enough to identify a PikaData. * * Internally the collection will likely be a single file name, therefore * @collection will be constructed from the file name (if it exists, or an * opaque identifier string otherwise, for internal data). Nevertheless you * should not take this for granted and should always use this string as an * opaque identifier only to be reused in pika_data_identify(). **/ void pika_data_get_identifiers (PikaData *data, gchar **name, gchar **collection, gboolean *is_internal) { *collection = pika_data_get_collection (data); *name = g_strdup (pika_object_get_name (PIKA_OBJECT (data))); *is_internal = pika_data_is_internal (data); } /** * pika_data_error_quark: * * This function is used to implement the PIKA_DATA_ERROR macro. It * shouldn't be called directly. * * Returns: the #GQuark to identify error in the PikaData error domain. **/ GQuark pika_data_error_quark (void) { return g_quark_from_static_string ("pika-data-error-quark"); }