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