Drop util_clip_region()
[geeqie.git] / src / metadata.cc
index 31804c7..4ba1dcd 100644 (file)
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#include "metadata.h"
+
+#include <unistd.h>
+
+#include <algorithm>
+#include <array>
 #include <clocale>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
 
-#include "main.h"
-#include "metadata.h"
+#include <glib-object.h>
+
+#include <config.h>
 
 #include "cache.h"
+#include "debug.h"
 #include "exif.h"
 #include "filedata.h"
+#include "intl.h"
+#include "layout-util.h"
+#include "main-defines.h"
 #include "misc.h"
+#include "options.h"
+#include "rcfile.h"
 #include "secure-save.h"
 #include "ui-fileops.h"
 #include "utilops.h"
-#include "layout-util.h"
-#include "rcfile.h"
+
+struct ExifData;
+
+namespace
+{
 
 enum MetadataKey {
        MK_NONE,
@@ -40,11 +59,16 @@ enum MetadataKey {
        MK_COMMENT
 };
 
+struct MetadataCacheEntry {
+       gchar *key;
+       GList *values;
+};
+
 /* If contents change, keep GuideOptionsMetadata.xml up to date */
 /**
  *  @brief Tags that will be written to all files in a group - selected by: options->metadata.sync_grouped_files, Preferences/Metadata/Write The Same Description Tags To All Grouped Sidecars
  */
-static const gchar *group_keys[] = {
+constexpr std::array<const gchar *, 22> group_keys{
        "Xmp.dc.title",
        "Xmp.photoshop.Urgency",
        "Xmp.photoshop.Category",
@@ -66,7 +90,33 @@ static const gchar *group_keys[] = {
        "Xmp.dc.rights",
        "Xmp.dc.description",
        "Xmp.photoshop.CaptionWriter",
-       nullptr};
+       "Xmp.xmp.Rating",
+};
+
+gint metadata_cache_entry_compare_key(const MetadataCacheEntry *entry, const gchar *key)
+{
+       return strcmp(entry->key, key);
+}
+
+void metadata_cache_entry_free(MetadataCacheEntry *entry)
+{
+       if (!entry) return;
+
+       g_free(entry->key);
+       g_list_free_full(entry->values, g_free);
+       g_free(entry);
+}
+
+inline gboolean is_keywords_separator(gchar c)
+{
+       return c == ','
+           || c == ';'
+           || c == '\n'
+           || c == '\r'
+           || c == '\b';
+}
+
+} // namespace
 
 static gboolean metadata_write_queue_idle_cb(gpointer data);
 static gboolean metadata_legacy_write(FileData *fd);
@@ -80,82 +130,64 @@ static gboolean metadata_file_read(gchar *path, GList **keywords, gchar **commen
  *-------------------------------------------------------------------
  */
 
-/* fd->cached metadata list of lists
-   each particular list contains key as a first entry, then the values
-*/
+/* fd->cached_metadata list of MetadataCacheEntry */
 
 static void metadata_cache_update(FileData *fd, const gchar *key, const GList *values)
 {
        GList *work;
 
-       work = fd->cached_metadata;
-       while (work)
+       work = g_list_find_custom(fd->cached_metadata, key, reinterpret_cast<GCompareFunc>(metadata_cache_entry_compare_key));
+       if (work)
                {
-               auto entry = static_cast<GList *>(work->data);
-               auto entry_key = static_cast<gchar *>(entry->data);
+               /* key found - just replace values */
+               auto *entry = static_cast<MetadataCacheEntry *>(work->data);
 
-               if (strcmp(entry_key, key) == 0)
-                       {
-                       /* key found - just replace values */
-                       GList *old_values = entry->next;
-                       entry->next = nullptr;
-                       old_values->prev = nullptr;
-                       g_list_free_full(old_values, g_free);
-                       work->data = g_list_append(entry, string_list_copy(values));
-                       DEBUG_1("updated %s %s\n", key, fd->path);
-                       return;
-                       }
-               work = work->next;
+               g_list_free_full(entry->values, g_free);
+               entry->values = string_list_copy(values);
+               DEBUG_1("updated %s %s\n", key, fd->path);
+               return;
                }
 
        /* key not found - prepend new entry */
-       fd->cached_metadata = g_list_prepend(fd->cached_metadata,
-                               g_list_prepend(string_list_copy(values), g_strdup(key)));
-       DEBUG_1("added %s %s\n", key, fd->path);
+       auto *entry = g_new0(MetadataCacheEntry, 1);
+       entry->key = g_strdup(key);
+       entry->values = string_list_copy(values);
 
+       fd->cached_metadata = g_list_prepend(fd->cached_metadata, entry);
+       DEBUG_1("added %s %s\n", key, fd->path);
 }
 
 static const GList *metadata_cache_get(FileData *fd, const gchar *key)
 {
        GList *work;
 
-       work = fd->cached_metadata;
-       while (work)
+       work = g_list_find_custom(fd->cached_metadata, key, reinterpret_cast<GCompareFunc>(metadata_cache_entry_compare_key));
+       if (work)
                {
-               auto entry = static_cast<GList *>(work->data);
-               auto entry_key = static_cast<gchar *>(entry->data);
+               /* key found */
+               auto *entry = static_cast<MetadataCacheEntry *>(work->data);
 
-               if (strcmp(entry_key, key) == 0)
-                       {
-                       /* key found */
-                       DEBUG_1("found %s %s\n", key, fd->path);
-                       return entry;
-                       }
-               work = work->next;
+               DEBUG_1("found %s %s\n", key, fd->path);
+               return entry->values;
                }
-       return nullptr;
        DEBUG_1("not found %s %s\n", key, fd->path);
+       return nullptr;
 }
 
 static void metadata_cache_remove(FileData *fd, const gchar *key)
 {
        GList *work;
 
-       work = fd->cached_metadata;
-       while (work)
+       work = g_list_find_custom(fd->cached_metadata, key, reinterpret_cast<GCompareFunc>(metadata_cache_entry_compare_key));
+       if (work)
                {
-               auto entry = static_cast<GList *>(work->data);
-               auto entry_key = static_cast<gchar *>(entry->data);
+               /* key found */
+               auto *entry = static_cast<MetadataCacheEntry *>(work->data);
 
-               if (strcmp(entry_key, key) == 0)
-                       {
-                       /* key found */
-                       g_list_free_full(entry, g_free);
-                       fd->cached_metadata = g_list_delete_link(fd->cached_metadata, work);
-                       DEBUG_1("removed %s %s\n", key, fd->path);
-                       return;
-                       }
-               work = work->next;
+               metadata_cache_entry_free(entry);
+               fd->cached_metadata = g_list_delete_link(fd->cached_metadata, work);
+               DEBUG_1("removed %s %s\n", key, fd->path);
+               return;
                }
        DEBUG_1("not removed %s %s\n", key, fd->path);
 }
@@ -164,19 +196,11 @@ void metadata_cache_free(FileData *fd)
 {
        if (fd->cached_metadata) DEBUG_1("freed %s\n", fd->path);
 
-       g_list_free_full(fd->cached_metadata, [](gpointer data)
-               {
-               auto entry = static_cast<GList *>(data);
-               g_list_free_full(entry, g_free);
-               });
+       g_list_free_full(fd->cached_metadata, reinterpret_cast<GDestroyNotify>(metadata_cache_entry_free));
        fd->cached_metadata = nullptr;
 }
 
 
-
-
-
-
 /*
  *-------------------------------------------------------------------
  * write queue
@@ -235,7 +259,7 @@ gboolean metadata_write_queue_remove_list_unused(GList *list)
        work = list;
        while (work)
                {
-               FileData *fd = static_cast<FileData *>(work->data);
+               auto *fd = static_cast<FileData *>(work->data);
                work = work->next;
                ret = ret && metadata_write_queue_remove(fd);
                }
@@ -243,7 +267,7 @@ gboolean metadata_write_queue_remove_list_unused(GList *list)
 }
 #pragma GCC diagnostic pop
 
-void metadata_notify_cb(FileData *fd, NotifyType type, gpointer UNUSED(data))
+void metadata_notify_cb(FileData *fd, NotifyType type, gpointer)
 {
        if (type & (NOTIFY_REREAD | NOTIFY_CHANGE))
                {
@@ -289,7 +313,7 @@ gboolean metadata_write_queue_confirm(gboolean force_dialog, FileUtilDoneFunc do
        return (metadata_write_queue != nullptr);
 }
 
-static gboolean metadata_write_queue_idle_cb(gpointer UNUSED(data))
+static gboolean metadata_write_queue_idle_cb(gpointer)
 {
        metadata_write_queue_confirm(FALSE, nullptr, nullptr);
        metadata_write_idle_id = 0;
@@ -343,18 +367,6 @@ gint metadata_queue_length()
        return g_list_length(metadata_write_queue);
 }
 
-static gboolean metadata_check_key(const gchar *keys[], const gchar *key)
-{
-       const gchar **k = keys;
-
-       while (*k)
-               {
-               if (strcmp(key, *k) == 0) return TRUE;
-               k++;
-               }
-       return FALSE;
-}
-
 gboolean metadata_write_revert(FileData *fd, const gchar *key)
 {
        if (!fd->modified_xmp) return FALSE;
@@ -392,7 +404,8 @@ gboolean metadata_write_list(FileData *fd, const gchar *key, const GList *values
        file_data_increment_version(fd);
        file_data_send_notification(fd, NOTIFY_METADATA);
 
-       if (options->metadata.sync_grouped_files && metadata_check_key(group_keys, key))
+       auto metadata_check_key = [key](const gchar *k) { return strcmp(key, k) == 0; };
+       if (options->metadata.sync_grouped_files && std::any_of(group_keys.cbegin(), group_keys.cend(), metadata_check_key))
                {
                GList *work = fd->sidecar_files;
 
@@ -606,7 +619,7 @@ static void metadata_legacy_delete(FileData *fd, const gchar *except)
                g_free(metadata_path);
                }
 
-#ifdef HAVE_EXIV2
+#if HAVE_EXIV2
        /* without exiv2: do not delete xmp metadata because we are not able to convert it,
           just ignore it */
        metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
@@ -669,21 +682,21 @@ GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
 {
        ExifData *exif;
        GList *list = nullptr;
-       const GList *cache_entry;
+       const GList *cache_values;
        if (!fd) return nullptr;
 
        /* unwritten data override everything */
        if (fd->modified_xmp && format == METADATA_PLAIN)
                {
-               list = static_cast<GList *>(g_hash_table_lookup(fd->modified_xmp, key));
+               list = static_cast<GList *>(g_hash_table_lookup(fd->modified_xmp, key));
                if (list) return string_list_copy(list);
                }
 
 
        if (format == METADATA_PLAIN && strcmp(key, KEYWORD_KEY) == 0
-           && (cache_entry = metadata_cache_get(fd, key)))
+           && (cache_values = metadata_cache_get(fd, key)))
                {
-               return string_list_copy(cache_entry->next);
+               return string_list_copy(cache_values);
                }
 
        /*
@@ -713,7 +726,7 @@ GList *metadata_read_list(FileData *fd, const gchar *key, MetadataFormat format)
                {
                return g_list_append(nullptr, metadata_file_info(fd, key, format));
                }
-#ifdef HAVE_LUA
+#if HAVE_LUA
        else if (strncmp(key, "lua.", 4) == 0)
                {
                return g_list_append(nullptr, metadata_lua_info(fd, key, format));
@@ -773,7 +786,9 @@ gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback
 {
        gdouble coord;
        gchar *endptr;
-       gdouble deg, min, sec;
+       gdouble deg;
+       gdouble min;
+       gdouble sec;
        gboolean ok = FALSE;
        gchar *string = metadata_read_string(fd, key, METADATA_PLAIN);
        if (!string) return fallback;
@@ -845,14 +860,12 @@ gboolean metadata_append_string(FileData *fd, const gchar *key, const char *valu
                {
                return metadata_write_string(fd, key, value);
                }
-       else
-               {
-               gchar *new_string = g_strconcat(str, value, NULL);
-               gboolean ret = metadata_write_string(fd, key, new_string);
-               g_free(str);
-               g_free(new_string);
-               return ret;
-               }
+
+       gchar *new_string = g_strconcat(str, value, NULL);
+       gboolean ret = metadata_write_string(fd, key, new_string);
+       g_free(str);
+       g_free(new_string);
+       return ret;
 }
 
 gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
@@ -863,7 +876,8 @@ gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value)
        char *coordinate;
        const char *ref;
        gboolean ok = TRUE;
-       char *old_locale, *saved_locale;
+       char *old_locale;
+       char *saved_locale;
 
        param = value;
        if (param < 0)
@@ -916,16 +930,14 @@ gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *value
                {
                return metadata_write_list(fd, key, values);
                }
-       else
-               {
-               gboolean ret;
-               list = g_list_concat(list, string_list_copy(values));
-               list = remove_duplicate_strings_from_list(list);
 
-               ret = metadata_write_list(fd, key, list);
-               g_list_free_full(list, g_free);
-               return ret;
-               }
+       gboolean ret;
+       list = g_list_concat(list, string_list_copy(values));
+       list = remove_duplicate_strings_from_list(list);
+
+       ret = metadata_write_list(fd, key, list);
+       g_list_free_full(list, g_free);
+       return ret;
 }
 
 /**
@@ -997,11 +1009,9 @@ gchar *find_string_in_list(GList *list, const gchar *string)
 {
        if (options->metadata.keywords_case_sensitive)
                return find_string_in_list_utf8case(list, string);
-       else
-               return find_string_in_list_utf8nocase(list, string);
-}
 
-#define KEYWORDS_SEPARATOR(c) ((c) == ',' || (c) == ';' || (c) == '\n' || (c) == '\r' || (c) == '\b')
+       return find_string_in_list_utf8nocase(list, string);
+}
 
 GList *string_to_keywords_list(const gchar *text)
 {
@@ -1013,9 +1023,9 @@ GList *string_to_keywords_list(const gchar *text)
                const gchar *begin;
                gint l = 0;
 
-               while (KEYWORDS_SEPARATOR(*ptr)) ptr++;
+               while (is_keywords_separator(*ptr)) ptr++;
                begin = ptr;
-               while (*ptr != '\0' && !KEYWORDS_SEPARATOR(*ptr))
+               while (*ptr != '\0' && !is_keywords_separator(*ptr))
                        {
                        ptr++;
                        l++;
@@ -1045,7 +1055,7 @@ GList *string_to_keywords_list(const gchar *text)
  */
 
 
-gboolean meta_data_get_keyword_mark(FileData *fd, gint UNUSED(n), gpointer data)
+gboolean meta_data_get_keyword_mark(FileData *fd, gint, gpointer data)
 {
        /** @FIXME do not use global keyword_tree */
        auto path = static_cast<GList *>(data);
@@ -1063,7 +1073,7 @@ gboolean meta_data_get_keyword_mark(FileData *fd, gint UNUSED(n), gpointer data)
        return found;
 }
 
-gboolean meta_data_set_keyword_mark(FileData *fd, gint UNUSED(n), gboolean value, gpointer data)
+gboolean meta_data_set_keyword_mark(FileData *fd, gint, gboolean value, gpointer data)
 {
        auto path = static_cast<GList *>(data);
        GList *keywords = nullptr;
@@ -1205,10 +1215,8 @@ gboolean keyword_same_parent(GtkTreeModel *keyword_tree, GtkTreeIter *a, GtkTree
                {
                return keyword_compare(keyword_tree, &parent_a, &parent_b) == 0;
                }
-       else
-               {
-               return (!valid_pa && !valid_pb); /* both are toplevel */
-               }
+
+       return (!valid_pa && !valid_pb); /* both are toplevel */
 }
 
 gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, GtkTreeIter *sibling, const gchar *name, gboolean exclude_sibling, GtkTreeIter *result)
@@ -1269,7 +1277,9 @@ gboolean keyword_exists(GtkTreeModel *keyword_tree, GtkTreeIter *parent_ptr, Gtk
 void keyword_copy(GtkTreeStore *keyword_tree, GtkTreeIter *to, GtkTreeIter *from)
 {
 
-       gchar *mark, *name, *casefold;
+       gchar *mark;
+       gchar *name;
+       gchar *casefold;
        gboolean is_keyword;
 
        /* do not copy KEYWORD_COLUMN_HIDE_IN, it fully shows the new subtree */
@@ -1378,20 +1388,8 @@ static gboolean keyword_tree_is_set_casefold(GtkTreeModel *keyword_tree, GtkTree
 
                if (keyword_get_is_keyword(keyword_tree, &iter))
                        {
-                       GList *work = casefold_list;
-                       gboolean found = FALSE;
                        gchar *iter_casefold = keyword_get_casefold(keyword_tree, &iter);
-                       while (work)
-                               {
-                               auto casefold = static_cast<const gchar *>(work->data);
-                               work = work->next;
-
-                               if (strcmp(iter_casefold, casefold) == 0)
-                                       {
-                                       found = TRUE;
-                                       break;
-                                       }
-                               }
+                       GList *found = g_list_find_custom(casefold_list, iter_casefold, reinterpret_cast<GCompareFunc>(strcmp));
                        g_free(iter_casefold);
                        if (!found) return FALSE;
                        }
@@ -1425,20 +1423,8 @@ static gboolean keyword_tree_is_set_casefull(GtkTreeModel *keyword_tree, GtkTree
 
                if (keyword_get_is_keyword(keyword_tree, &iter))
                        {
-                       GList *work = kw_list;
-                       gboolean found = FALSE;
                        gchar *iter_name = keyword_get_name(keyword_tree, &iter);
-                       while (work)
-                               {
-                               auto name = static_cast<const gchar *>(work->data);
-                               work = work->next;
-
-                               if (strcmp(iter_name, name) == 0)
-                                       {
-                                       found = TRUE;
-                                       break;
-                                       }
-                               }
+                       GList *found = g_list_find_custom(kw_list, iter_name, reinterpret_cast<GCompareFunc>(strcmp));
                        g_free(iter_name);
                        if (!found) return FALSE;
                        }
@@ -1629,7 +1615,7 @@ gboolean keyword_is_hidden_in(GtkTreeModel *keyword_tree, GtkTreeIter *iter, gpo
        return !!g_list_find(list, id);
 }
 
-static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *UNUSED(path), GtkTreeIter *iter, gpointer data)
+static gboolean keyword_show_all_in_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
 {
        keyword_show_in(GTK_TREE_STORE(model), iter, data);
        return FALSE;
@@ -1640,7 +1626,7 @@ void keyword_show_all_in(GtkTreeStore *keyword_tree, gpointer id)
        gtk_tree_model_foreach(GTK_TREE_MODEL(keyword_tree), keyword_show_all_in_cb, id);
 }
 
-static gboolean keyword_revert_hidden_in_cb(GtkTreeModel *model, GtkTreePath *UNUSED(path), GtkTreeIter *iter, gpointer data)
+static gboolean keyword_revert_hidden_in_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter, gpointer data)
 {
        if (keyword_is_hidden_in(GTK_TREE_MODEL(keyword_tree), iter, data))
                {
@@ -1683,7 +1669,7 @@ void keyword_hide_unset_in(GtkTreeStore *keyword_tree, gpointer id, GList *keywo
        keyword_hide_unset_in_recursive(keyword_tree, &iter, id, keywords);
 }
 
-static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *UNUSED(path), GtkTreeIter *iter_ptr, gpointer data)
+static gboolean keyword_show_set_in_cb(GtkTreeModel *model, GtkTreePath *, GtkTreeIter *iter_ptr, gpointer data)
 {
        GtkTreeIter iter = *iter_ptr;
        auto keywords = static_cast<GList *>(data);
@@ -1728,7 +1714,8 @@ static GtkTreeIter keyword_tree_default_append(GtkTreeStore *keyword_tree, GtkTr
 
 void keyword_tree_new_default()
 {
-       GtkTreeIter i1, i2;
+       GtkTreeIter i1;
+       GtkTreeIter i2;
 
        if (!keyword_tree) keyword_tree_new();