1 /*
2     This file is part of darktable,
3     Copyright (C) 2010-2021 darktable developers.
4 
5     darktable is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     darktable is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "common/metadata.h"
20 #include "common/debug.h"
21 #include "common/collection.h"
22 #include "common/undo.h"
23 #include "common/grouping.h"
24 #include "control/conf.h"
25 #include "views/view.h"
26 #include "control/signal.h"
27 
28 #include <stdlib.h>
29 
30 // this array should contain all dt metadata
31 // add the new metadata at the end when needed
32 // Dependencies
33 //    Must match with dt_metadata_t in metadata.h.
34 //    Exif.cc: add the new metadata into dt_xmp_keys[]
35 //    libs/metadata.c increment version and change legacy_param() accordingly
36 // CAUTION : key, subkey (last term of key) & name must be unique
37 
38 static const struct
39 {
40   char *key;
41   char *name;
42   int type;
43   uint32_t display_order;
44 } dt_metadata_def[] = {
45   // clang-format off
46   {"Xmp.dc.creator", N_("creator"), DT_METADATA_TYPE_USER, 2},
47   {"Xmp.dc.publisher", N_("publisher"), DT_METADATA_TYPE_USER, 3},
48   {"Xmp.dc.title", N_("title"), DT_METADATA_TYPE_USER, 0},
49   {"Xmp.dc.description", N_("description"), DT_METADATA_TYPE_USER, 1},
50   {"Xmp.dc.rights", N_("rights"), DT_METADATA_TYPE_USER, 4},
51   {"Xmp.acdsee.notes", N_("notes"), DT_METADATA_TYPE_USER, 5},
52   {"Xmp.darktable.version_name", N_("version name"), DT_METADATA_TYPE_OPTIONAL, 6}
53   // clang-format on
54 };
55 
dt_metadata_get_name_by_display_order(const uint32_t order)56 const char *dt_metadata_get_name_by_display_order(const uint32_t order)
57 {
58   if(order < DT_METADATA_NUMBER)
59   {
60     for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
61     {
62       if(order == dt_metadata_def[i].display_order)
63         return dt_metadata_def[i].name;
64     }
65   }
66   return NULL;
67 }
68 
dt_metadata_get_keyid_by_display_order(const uint32_t order)69 dt_metadata_t dt_metadata_get_keyid_by_display_order(const uint32_t order)
70 {
71   if(order < DT_METADATA_NUMBER)
72   {
73     for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
74     {
75       if(order == dt_metadata_def[i].display_order)
76         return i;
77     }
78   }
79   return -1;
80 }
81 
dt_metadata_get_keyid_by_name(const char * name)82 dt_metadata_t dt_metadata_get_keyid_by_name(const char* name)
83 {
84   if(!name) return -1;
85   for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
86   {
87     if(strncmp(name, dt_metadata_def[i].name, strlen(dt_metadata_def[i].name)) == 0)
88       return i;
89   }
90   return -1;
91 }
92 
dt_metadata_get_type_by_display_order(const uint32_t order)93 int dt_metadata_get_type_by_display_order(const uint32_t order)
94 {
95   if(order < DT_METADATA_NUMBER)
96   {
97     for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
98     {
99       if(order == dt_metadata_def[i].display_order)
100         return dt_metadata_def[i].type;
101     }
102   }
103   return 0;
104 }
105 
dt_metadata_get_name(const uint32_t keyid)106 const char *dt_metadata_get_name(const uint32_t keyid)
107 {
108   if(keyid < DT_METADATA_NUMBER)
109     return dt_metadata_def[keyid].name;
110   else
111     return NULL;
112 }
113 
dt_metadata_get_keyid(const char * key)114 dt_metadata_t dt_metadata_get_keyid(const char* key)
115 {
116   if(!key) return -1;
117   for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
118   {
119     if(strncmp(key, dt_metadata_def[i].key, strlen(dt_metadata_def[i].key)) == 0)
120       return i;
121   }
122   return -1;
123 }
124 
dt_metadata_get_key(const uint32_t keyid)125 const char *dt_metadata_get_key(const uint32_t keyid)
126 {
127   if(keyid < DT_METADATA_NUMBER)
128     return dt_metadata_def[keyid].key;
129   else
130     return NULL;
131 }
132 
dt_metadata_get_subkey(const uint32_t keyid)133 const char *dt_metadata_get_subkey(const uint32_t keyid)
134 {
135   if(keyid < DT_METADATA_NUMBER)
136   {
137     char *t = g_strrstr(dt_metadata_def[keyid].key, ".");
138     if(t) return t + 1;
139   }
140   return NULL;
141 }
142 
dt_metadata_get_key_by_subkey(const char * subkey)143 const char *dt_metadata_get_key_by_subkey(const char *subkey)
144 {
145   if(subkey)
146   {
147     for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
148     {
149       char *t = g_strrstr(dt_metadata_def[i].key, ".");
150       if(t && !g_strcmp0(t + 1, subkey))
151         return dt_metadata_def[i].key;
152     }
153   }
154   return NULL;
155 }
156 
dt_metadata_get_type(const uint32_t keyid)157 int dt_metadata_get_type(const uint32_t keyid)
158 {
159   if(keyid < DT_METADATA_NUMBER)
160     return dt_metadata_def[keyid].type;
161   else
162     return 0;
163 }
164 
dt_metadata_init()165 void dt_metadata_init()
166 {
167   for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
168   {
169     const int type = dt_metadata_get_type(i);
170     const char *name = (gchar *)dt_metadata_get_name(i);
171     char *setting = dt_util_dstrcat(NULL, "plugins/lighttable/metadata/%s_flag", name);
172     if(!dt_conf_key_exists(setting))
173     {
174       // per default should be imported - ignored if "write_sidecar_files" set
175       uint32_t flag = DT_METADATA_FLAG_IMPORTED;
176       if(type == DT_METADATA_TYPE_OPTIONAL)
177       {
178         // per default this one should be hidden
179         flag |= DT_METADATA_FLAG_HIDDEN;
180       }
181       dt_conf_set_int(setting, flag);
182     }
183     g_free(setting);
184   }
185 }
186 
187 typedef struct dt_undo_metadata_t
188 {
189   int imgid;
190   GList *before;      // list of key/value before
191   GList *after;       // list of key/value after
192 } dt_undo_metadata_t;
193 
_list_find_custom(GList * list,gpointer data)194 static GList *_list_find_custom(GList *list, gpointer data)
195 {
196   for(GList *i = list; i; i = g_list_next(i))
197   {
198     if(i->data && !g_strcmp0(i->data, data))
199       return i;
200     i = g_list_next(i);
201   }
202   return NULL;
203 }
204 
_get_tb_removed_metadata_string_values(GList * before,GList * after)205 static gchar *_get_tb_removed_metadata_string_values(GList *before, GList *after)
206 {
207   GList *b = before;
208   GList *a = after;
209   gchar *metadata_list = NULL;
210 
211   while(b)
212   {
213     GList *same_key = _list_find_custom(a, b->data);
214     GList *b2 = g_list_next(b);
215     gboolean different_value = FALSE;
216     const char *value = (char *)b2->data; // if empty we can remove it
217     if(same_key)
218     {
219       GList *same2 = g_list_next(same_key);
220       different_value = g_strcmp0(same2->data, b2->data);
221     }
222     if(!same_key || different_value || !value[0])
223     {
224       metadata_list = dt_util_dstrcat(metadata_list, "%d,", atoi(b->data));
225     }
226     b = g_list_next(b);
227     b = g_list_next(b);
228   }
229   if(metadata_list) metadata_list[strlen(metadata_list) - 1] = '\0';
230   return metadata_list;
231 }
232 
_get_tb_added_metadata_string_values(const int img,GList * before,GList * after)233 static gchar *_get_tb_added_metadata_string_values(const int img, GList *before, GList *after)
234 {
235   GList *b = before;
236   GList *a = after;
237   gchar *metadata_list = NULL;
238 
239   while(a)
240   {
241     GList *same_key = _list_find_custom(b, a->data);
242     GList *a2 = g_list_next(a);
243     gboolean different_value = FALSE;
244     const char *value = (char *)a2->data; // if empty we don't add it to database
245     if(same_key)
246     {
247       GList *same2 = g_list_next(same_key);
248       different_value = g_strcmp0(same2->data, a2->data);
249     }
250     if((!same_key || different_value) && value[0])
251     {
252       char *escaped_text = sqlite3_mprintf("%q", value);
253       metadata_list = dt_util_dstrcat(metadata_list, "(%d,%d,'%s'),", GPOINTER_TO_INT(img), atoi(a->data), escaped_text);
254       sqlite3_free(escaped_text);
255     }
256     a = g_list_next(a);
257     a = g_list_next(a);
258   }
259   if(metadata_list) metadata_list[strlen(metadata_list) - 1] = '\0';
260   return metadata_list;
261 }
262 
_bulk_remove_metadata(const int img,const gchar * metadata_list)263 static void _bulk_remove_metadata(const int img, const gchar *metadata_list)
264 {
265   if(img > 0 && metadata_list)
266   {
267     char *query = NULL;
268     sqlite3_stmt *stmt;
269     query = dt_util_dstrcat(query, "DELETE FROM main.meta_data WHERE id = %d AND key IN (%s)", img, metadata_list);
270     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
271     sqlite3_step(stmt);
272     sqlite3_finalize(stmt);
273     g_free(query);
274   }
275 }
276 
_bulk_add_metadata(gchar * metadata_list)277 static void _bulk_add_metadata(gchar *metadata_list)
278 {
279   if(metadata_list)
280   {
281     char *query = NULL;
282     sqlite3_stmt *stmt;
283     query = dt_util_dstrcat(query, "INSERT INTO main.meta_data (id, key, value) VALUES %s", metadata_list);
284     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
285     sqlite3_step(stmt);
286     sqlite3_finalize(stmt);
287     g_free(query);
288   }
289 }
290 
_pop_undo_execute(const int imgid,GList * before,GList * after)291 static void _pop_undo_execute(const int imgid, GList *before, GList *after)
292 {
293   gchar *tobe_removed_list = _get_tb_removed_metadata_string_values(before, after);
294   gchar *tobe_added_list = _get_tb_added_metadata_string_values(imgid, before, after);
295 
296   _bulk_remove_metadata(imgid, tobe_removed_list);
297   _bulk_add_metadata(tobe_added_list);
298 
299   g_free(tobe_removed_list);
300   g_free(tobe_added_list);
301 }
302 
_pop_undo(gpointer user_data,const dt_undo_type_t type,dt_undo_data_t data,const dt_undo_action_t action,GList ** imgs)303 static void _pop_undo(gpointer user_data, const dt_undo_type_t type, dt_undo_data_t data, const dt_undo_action_t action, GList **imgs)
304 {
305   if(type == DT_UNDO_METADATA)
306   {
307     for(GList *list = (GList *)data; list; list = g_list_next(list))
308     {
309       dt_undo_metadata_t *undometadata = (dt_undo_metadata_t *)list->data;
310 
311       GList *before = (action == DT_ACTION_UNDO) ? undometadata->after : undometadata->before;
312       GList *after = (action == DT_ACTION_UNDO) ? undometadata->before : undometadata->after;
313       _pop_undo_execute(undometadata->imgid, before, after);
314       *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undometadata->imgid));
315     }
316 
317     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE);
318   }
319 }
320 
dt_metadata_get_list_id(const int id)321 GList *dt_metadata_get_list_id(const int id)
322 {
323   GList *metadata = NULL;
324   sqlite3_stmt *stmt;
325   DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
326                               "SELECT key, value FROM main.meta_data WHERE id=?1", -1, &stmt, NULL);
327   DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, id);
328   while(sqlite3_step(stmt) == SQLITE_ROW)
329   {
330     const gchar *value = (const char *)sqlite3_column_text(stmt, 1);
331     const gchar *ckey = dt_util_dstrcat(NULL, "%d", sqlite3_column_int(stmt, 0));
332     const gchar *cvalue = g_strdup(value ? value : ""); // to avoid NULL value
333     metadata = g_list_append(metadata, (gpointer)ckey);
334     metadata = g_list_append(metadata, (gpointer)cvalue);
335   }
336   sqlite3_finalize(stmt);
337   return metadata;
338 }
339 
_undo_metadata_free(gpointer data)340 static void _undo_metadata_free(gpointer data)
341 {
342   dt_undo_metadata_t *metadata = (dt_undo_metadata_t *)data;
343   g_list_free_full(metadata->before, g_free);
344   g_list_free_full(metadata->after, g_free);
345   g_free(metadata);
346 }
347 
_metadata_undo_data_free(gpointer data)348 static void _metadata_undo_data_free(gpointer data)
349 {
350   GList *l = (GList *)data;
351   g_list_free_full(l, _undo_metadata_free);
352 }
353 
_cleanup_metadata_value(const gchar * value)354 gchar *_cleanup_metadata_value(const gchar *value)
355 {
356   char *v = NULL;
357   char *c = NULL;
358   if (value && value[0])
359   {
360     v = g_strdup(value);
361     c = v + strlen(v) - 1;
362     while(c >= v && *c == ' ') *c-- = '\0';
363     c = v;
364     while(*c == ' ') c++;
365   }
366   c = g_strdup(c ? c : ""); // avoid NULL value
367   g_free(v);
368   return c;
369 }
370 
dt_metadata_get(const int id,const char * key,uint32_t * count)371 GList *dt_metadata_get(const int id, const char *key, uint32_t *count)
372 {
373   GList *result = NULL;
374   sqlite3_stmt *stmt;
375   uint32_t local_count = 0;
376 
377   const int keyid = dt_metadata_get_keyid(key);
378   // key not found in db. Maybe it's one of our "special" keys (rating, tags and colorlabels)?
379   if(keyid == -1)
380   {
381     if(strncmp(key, "Xmp.xmp.Rating", 14) == 0)
382     {
383       if(id == -1)
384       {
385         DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT flags FROM main.images WHERE id IN "
386                                                                    "(SELECT imgid FROM main.selected_images)",
387                                     -1, &stmt, NULL);
388       }
389       else // single image under mouse cursor
390       {
391         DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT flags FROM main.images WHERE id = ?1",
392                                     -1, &stmt, NULL);
393         DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, id);
394       }
395       while(sqlite3_step(stmt) == SQLITE_ROW)
396       {
397         local_count++;
398         int stars = sqlite3_column_int(stmt, 0);
399         stars = (stars & 0x7) - 1;
400         result = g_list_prepend(result, GINT_TO_POINTER(stars));
401       }
402       sqlite3_finalize(stmt);
403     }
404     else if(strncmp(key, "Xmp.dc.subject", 14) == 0)
405     {
406       if(id == -1)
407       {
408         DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
409                                     "SELECT name FROM data.tags t JOIN main.tagged_images i ON "
410                                     "i.tagid = t.id WHERE imgid IN "
411                                     "(SELECT imgid FROM main.selected_images)",
412                                     -1, &stmt, NULL);
413       }
414       else // single image under mouse cursor
415       {
416         DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
417                                     "SELECT name FROM data.tags t JOIN main.tagged_images i ON "
418                                     "i.tagid = t.id WHERE imgid = ?1",
419                                     -1, &stmt, NULL);
420         DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, id);
421       }
422       while(sqlite3_step(stmt) == SQLITE_ROW)
423       {
424         local_count++;
425         result = g_list_prepend(result, g_strdup((char *)sqlite3_column_text(stmt, 0)));
426       }
427       sqlite3_finalize(stmt);
428     }
429     else if(strncmp(key, "Xmp.darktable.colorlabels", 25) == 0)
430     {
431       if(id == -1)
432       {
433         DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
434                                     "SELECT color FROM main.color_labels WHERE imgid IN "
435                                     "(SELECT imgid FROM main.selected_images)",
436                                     -1, &stmt, NULL);
437       }
438       else // single image under mouse cursor
439       {
440         DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
441                                     "SELECT color FROM main.color_labels WHERE imgid=?1 ORDER BY color",
442                                     -1, &stmt, NULL);
443         DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, id);
444       }
445       while(sqlite3_step(stmt) == SQLITE_ROW)
446       {
447         local_count++;
448         result = g_list_prepend(result, GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
449       }
450       sqlite3_finalize(stmt);
451     }
452     if(count != NULL) *count = local_count;
453     return g_list_reverse(result);
454   }
455 
456   // So we got this far -- it has to be a generic key-value entry from meta_data
457   if(id == -1)
458   {
459     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
460                                 "SELECT value FROM main.meta_data WHERE id IN "
461                                 "(SELECT imgid FROM main.selected_images) AND key = ?1 ORDER BY value",
462                                 -1, &stmt, NULL);
463     DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, keyid);
464   }
465   else // single image under mouse cursor
466   {
467     DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
468                                 "SELECT value FROM main.meta_data WHERE id = ?1 AND key = ?2 ORDER BY value", -1,
469                                 &stmt, NULL);
470     DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, id);
471     DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, keyid);
472   }
473   while(sqlite3_step(stmt) == SQLITE_ROW)
474   {
475     local_count++;
476     char *value = (char *)sqlite3_column_text(stmt, 0);
477     result = g_list_prepend(result, g_strdup(value ? value : "")); // to avoid NULL value
478   }
479   sqlite3_finalize(stmt);
480   if(count != NULL) *count = local_count;
481   return g_list_reverse(result);  // list was built in reverse order, so un-reverse it
482 }
483 
_metadata_add_metadata_to_list(GList ** list,const GList * metadata)484 static void _metadata_add_metadata_to_list(GList **list, const GList *metadata)
485 {
486   const GList *m = metadata;
487   while(m)
488   {
489     GList *m2 = g_list_next(m);
490     GList *same_key = _list_find_custom(*list, m->data);
491     GList *same2 = g_list_next(same_key);
492     gboolean different_value = FALSE;
493     if(same_key) different_value = g_strcmp0(same2->data, m2->data);
494     if(same_key && different_value)
495     {
496       // same key but different value - replace the old value by the new one
497       g_free(same2->data);
498       same2->data = g_strdup(m2->data);
499     }
500     else if(!same_key)
501     {
502       // new key for that image - append the new metadata item
503       *list = g_list_append(*list, g_strdup(m->data));
504       *list = g_list_append(*list, g_strdup(m2->data));
505     }
506     m = g_list_next(m);
507     m = g_list_next(m);
508   }
509 }
510 
_metadata_remove_metadata_from_list(GList ** list,const GList * metadata)511 static void _metadata_remove_metadata_from_list(GList **list, const GList *metadata)
512 {
513   // caution: metadata is a simple list here
514   for(const GList *m = metadata; m; m = g_list_next(m))
515   {
516     GList *same_key = _list_find_custom(*list, m->data);
517     if(same_key)
518     {
519       // same key for that image - remove metadata item
520       GList *same2 = g_list_next(same_key);
521       *list = g_list_remove_link(*list, same_key);
522       g_free(same_key->data);
523       g_list_free(same_key);
524       *list = g_list_remove_link(*list, same2);
525       g_free(same2->data);
526       g_list_free(same2);
527     }
528   }
529 }
530 
531 typedef enum dt_tag_actions_t
532 {
533   DT_MA_SET = 0,
534   DT_MA_ADD,
535   DT_MA_REMOVE
536 } dt_tag_actions_t;
537 
_metadata_execute(const GList * imgs,const GList * metadata,GList ** undo,const gboolean undo_on,const gint action)538 static void _metadata_execute(const GList *imgs, const GList *metadata, GList **undo,
539                               const gboolean undo_on, const gint action)
540 {
541   for(const GList *images = imgs; images; images = g_list_next(images))
542   {
543     const int image_id = GPOINTER_TO_INT(images->data);
544 
545     dt_undo_metadata_t *undometadata = (dt_undo_metadata_t *)malloc(sizeof(dt_undo_metadata_t));
546     undometadata->imgid = image_id;
547     undometadata->before = dt_metadata_get_list_id(image_id);
548     switch(action)
549     {
550       case DT_MA_SET:
551         undometadata->after = metadata ? g_list_copy_deep((GList *)metadata, (GCopyFunc)g_strdup, NULL) : NULL;
552         break;
553       case DT_MA_ADD:
554         undometadata->after = g_list_copy_deep(undometadata->before, (GCopyFunc)g_strdup, NULL);
555         _metadata_add_metadata_to_list(&undometadata->after, metadata);
556         break;
557       case DT_MA_REMOVE:
558         undometadata->after = g_list_copy_deep(undometadata->before, (GCopyFunc)g_strdup, NULL);
559         _metadata_remove_metadata_from_list(&undometadata->after, metadata);
560         break;
561       default:
562         undometadata->after = g_list_copy_deep(undometadata->before, (GCopyFunc)g_strdup, NULL);
563         break;
564     }
565 
566     _pop_undo_execute(image_id, undometadata->before, undometadata->after);
567 
568     if(undo_on)
569       *undo = g_list_append(*undo, undometadata);
570     else
571       _undo_metadata_free(undometadata);
572   }
573 }
574 
dt_metadata_set(const int imgid,const char * key,const char * value,const gboolean undo_on)575 void dt_metadata_set(const int imgid, const char *key, const char *value, const gboolean undo_on)
576 {
577   if(!key || !imgid) return;
578 
579   int keyid = dt_metadata_get_keyid(key);
580   if(keyid != -1) // known key
581   {
582     GList *imgs = NULL;
583     if(imgid == -1)
584       imgs = g_list_copy((GList *)dt_view_get_images_to_act_on(TRUE, TRUE, FALSE));
585     else
586       imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
587     if(imgs)
588     {
589       GList *undo = NULL;
590       if(undo_on) dt_undo_start_group(darktable.undo, DT_UNDO_METADATA);
591 
592       const gchar *ckey = dt_util_dstrcat(NULL, "%d", keyid);
593       const gchar *cvalue = _cleanup_metadata_value(value);
594       GList *metadata = NULL;
595       metadata = g_list_append(metadata, (gpointer)ckey);
596       metadata = g_list_append(metadata, (gpointer)cvalue);
597 
598       _metadata_execute(imgs, metadata, &undo, undo_on, DT_MA_ADD);
599 
600       g_list_free_full(metadata, g_free);
601       g_list_free(imgs);
602       if(undo_on)
603       {
604         dt_undo_record(darktable.undo, NULL, DT_UNDO_METADATA, undo, _pop_undo, _metadata_undo_data_free);
605         dt_undo_end_group(darktable.undo);
606       }
607     }
608   }
609 }
610 
dt_metadata_set_import(const int imgid,const char * key,const char * value)611 void dt_metadata_set_import(const int imgid, const char *key, const char *value)
612 {
613   if(!key || !imgid || imgid == -1) return;
614 
615   const int keyid = dt_metadata_get_keyid(key);
616 
617   if(keyid != -1) // known key
618   {
619     gboolean imported = dt_conf_get_bool("write_sidecar_files");
620     if(!imported && dt_metadata_get_type(keyid) != DT_METADATA_TYPE_INTERNAL)
621     {
622       const gchar *name = dt_metadata_get_name(keyid);
623       char *setting = dt_util_dstrcat(NULL, "plugins/lighttable/metadata/%s_flag", name);
624       imported = dt_conf_get_int(setting) & DT_METADATA_FLAG_IMPORTED;
625       g_free(setting);
626     }
627     if(imported)
628     {
629       GList *imgs = NULL;
630       imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
631       if(imgs)
632       {
633         GList *undo = NULL;
634 
635         const gchar *ckey = dt_util_dstrcat(NULL, "%d", keyid);
636         const gchar *cvalue = _cleanup_metadata_value(value);
637         GList *metadata = NULL;
638         metadata = g_list_append(metadata, (gpointer)ckey);
639         metadata = g_list_append(metadata, (gpointer)cvalue);
640 
641         _metadata_execute(imgs, metadata, &undo, FALSE, DT_MA_ADD);
642 
643         g_list_free_full(metadata, g_free);
644         g_list_free(imgs);
645       }
646     }
647   }
648 }
649 
dt_metadata_set_list(const GList * imgs,GList * key_value,const gboolean undo_on)650 void dt_metadata_set_list(const GList *imgs, GList *key_value, const gboolean undo_on)
651 {
652   GList *metadata = NULL;
653   GList *kv = key_value;
654   while(kv)
655   {
656     const gchar *key = (const gchar *)kv->data;
657     const int keyid = dt_metadata_get_keyid(key);
658     if(keyid != -1) // known key
659     {
660       const gchar *ckey = dt_util_dstrcat(NULL, "%d", keyid);
661       kv = g_list_next(kv);
662       const gchar *value = (const gchar *)kv->data;
663       kv = g_list_next(kv);
664       if(value)
665       {
666         metadata = g_list_append(metadata, (gchar *)ckey);
667         metadata = g_list_append(metadata, _cleanup_metadata_value(value));
668       }
669     }
670     else
671     {
672       kv = g_list_next(kv);
673       kv = g_list_next(kv);
674     }
675   }
676 
677   if(metadata && imgs)
678   {
679     GList *undo = NULL;
680     if(undo_on) dt_undo_start_group(darktable.undo, DT_UNDO_METADATA);
681 
682     _metadata_execute(imgs, metadata, &undo, undo_on, DT_MA_ADD);
683 
684     if(undo_on)
685     {
686       dt_undo_record(darktable.undo, NULL, DT_UNDO_METADATA, undo, _pop_undo, _metadata_undo_data_free);
687       dt_undo_end_group(darktable.undo);
688     }
689 
690     g_list_free_full(metadata, g_free);
691   }
692 }
693 
dt_metadata_clear(const GList * imgs,const gboolean undo_on)694 void dt_metadata_clear(const GList *imgs, const gboolean undo_on)
695 {
696   // do not clear internal or hidden metadata
697   GList *metadata = NULL;
698   for(unsigned int i = 0; i < DT_METADATA_NUMBER; i++)
699   {
700     if(dt_metadata_get_type(i) != DT_METADATA_TYPE_INTERNAL)
701     {
702       const gchar *name = dt_metadata_get_name(i);
703       char *setting = dt_util_dstrcat(NULL, "plugins/lighttable/metadata/%s_flag", name);
704       const gboolean hidden = dt_conf_get_int(setting) & DT_METADATA_FLAG_HIDDEN;
705       g_free(setting);
706       if(!hidden)
707       {
708         // caution: metadata is a simple list here
709         metadata = g_list_prepend(metadata, dt_util_dstrcat(NULL, "%d", i));
710       }
711     }
712   }
713 
714   if(metadata)
715   {
716     metadata = g_list_reverse(metadata);  // list was built in reverse order, so un-reverse it
717     GList *undo = NULL;
718     if(undo_on) dt_undo_start_group(darktable.undo, DT_UNDO_METADATA);
719 
720     _metadata_execute(imgs, metadata, &undo, undo_on, DT_MA_REMOVE);
721 
722     if(undo_on)
723     {
724       dt_undo_record(darktable.undo, NULL, DT_UNDO_METADATA, undo, _pop_undo, _metadata_undo_data_free);
725       dt_undo_end_group(darktable.undo);
726     }
727 
728     g_list_free_full(metadata, g_free);
729   }
730 }
731 
dt_metadata_set_list_id(const GList * img,const GList * metadata,const gboolean clear_on,const gboolean undo_on)732 void dt_metadata_set_list_id(const GList *img, const GList *metadata, const gboolean clear_on,
733                              const gboolean undo_on)
734 {
735   if(img)
736   {
737     GList *undo = NULL;
738     if(undo_on) dt_undo_start_group(darktable.undo, DT_UNDO_METADATA);
739 
740     _metadata_execute(img, metadata, &undo, undo_on, clear_on ? DT_MA_SET : DT_MA_ADD);
741 
742     if(undo_on)
743     {
744       dt_undo_record(darktable.undo, NULL, DT_UNDO_METADATA, undo, _pop_undo, _metadata_undo_data_free);
745       dt_undo_end_group(darktable.undo);
746     }
747   }
748 }
749 
750 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
751 // vim: shiftwidth=2 expandtab tabstop=2 cindent
752 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
753