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 #include "common/tags.h"
19 #include "common/collection.h"
20 #include "common/darktable.h"
21 #include "common/debug.h"
22 #include "common/grouping.h"
23 #include "common/selection.h"
24 #include "common/undo.h"
25 #include "control/conf.h"
26 #include "control/control.h"
27 #include <glib.h>
28 #if defined (_WIN32)
29 #include "win/getdelim.h"
30 #endif // defined (_WIN32)
31
32 typedef struct dt_undo_tags_t
33 {
34 int imgid;
35 GList *before; // list of tagid before
36 GList *after; // list of tagid after
37 } dt_undo_tags_t;
38
_get_tb_removed_tag_string_values(GList * before,GList * after)39 static gchar *_get_tb_removed_tag_string_values(GList *before, GList *after)
40 {
41 GList *a = after;
42 gchar *tag_list = NULL;
43 for(GList *b = before; b; b = g_list_next(b))
44 {
45 if(!g_list_find(a, b->data))
46 {
47 tag_list = dt_util_dstrcat(tag_list, "%d,", GPOINTER_TO_INT(b->data));
48 }
49 }
50 if(tag_list) tag_list[strlen(tag_list) - 1] = '\0';
51 return tag_list;
52 }
53
_get_tb_added_tag_string_values(const int img,GList * before,GList * after)54 static gchar *_get_tb_added_tag_string_values(const int img, GList *before, GList *after)
55 {
56 GList *b = before;
57 gchar *tag_list = NULL;
58 for(GList *a = after; a; a = g_list_next(a))
59 {
60 if(!g_list_find(b, a->data))
61 {
62 tag_list = dt_util_dstrcat(tag_list,
63 "(%d,%d,"
64 " (SELECT (IFNULL(MAX(position),0) & 0xFFFFFFFF00000000) + (1 << 32)"
65 " FROM main.tagged_images)"
66 "),",
67 GPOINTER_TO_INT(img),
68 GPOINTER_TO_INT(a->data));
69 }
70 }
71 if(tag_list) tag_list[strlen(tag_list) - 1] = '\0';
72 return tag_list;
73 }
74
_bulk_remove_tags(const int img,const gchar * tag_list)75 static void _bulk_remove_tags(const int img, const gchar *tag_list)
76 {
77 if(img > 0 && tag_list)
78 {
79 sqlite3_stmt *stmt;
80 gchar *query = g_strdup_printf("DELETE FROM main.tagged_images WHERE imgid = %d AND tagid IN (%s)", img, tag_list);
81 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
82 sqlite3_step(stmt);
83 sqlite3_finalize(stmt);
84 g_free(query);
85 }
86 }
87
_bulk_add_tags(const gchar * tag_list)88 static void _bulk_add_tags(const gchar *tag_list)
89 {
90 if(tag_list)
91 {
92 sqlite3_stmt *stmt;
93 gchar *query = g_strdup_printf("INSERT INTO main.tagged_images (imgid, tagid, position) VALUES %s", tag_list);
94 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
95 sqlite3_step(stmt);
96 sqlite3_finalize(stmt);
97 g_free(query);
98 }
99 }
100
_pop_undo_execute(const int imgid,GList * before,GList * after)101 static void _pop_undo_execute(const int imgid, GList *before, GList *after)
102 {
103 gchar *tobe_removed_list = _get_tb_removed_tag_string_values(before, after);
104 gchar *tobe_added_list = _get_tb_added_tag_string_values(imgid, before, after);
105
106 _bulk_remove_tags(imgid, tobe_removed_list);
107 _bulk_add_tags(tobe_added_list);
108
109 g_free(tobe_removed_list);
110 g_free(tobe_added_list);
111 }
112
_pop_undo(gpointer user_data,dt_undo_type_t type,dt_undo_data_t data,dt_undo_action_t action,GList ** imgs)113 static void _pop_undo(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
114 {
115 if(type == DT_UNDO_TAGS)
116 {
117 for(GList *list = (GList *)data; list; list = g_list_next(list))
118 {
119 dt_undo_tags_t *undotags = (dt_undo_tags_t *)list->data;
120
121 GList *before = (action == DT_ACTION_UNDO) ? undotags->after : undotags->before;
122 GList *after = (action == DT_ACTION_UNDO) ? undotags->before : undotags->after;
123 _pop_undo_execute(undotags->imgid, before, after);
124 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undotags->imgid));
125 }
126
127 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_TAG_CHANGED);
128 }
129 }
130
_undo_tags_free(gpointer data)131 static void _undo_tags_free(gpointer data)
132 {
133 dt_undo_tags_t *undotags = (dt_undo_tags_t *)data;
134 g_list_free(undotags->before);
135 g_list_free(undotags->after);
136 g_free(undotags);
137 }
138
_tags_undo_data_free(gpointer data)139 static void _tags_undo_data_free(gpointer data)
140 {
141 GList *l = (GList *)data;
142 g_list_free_full(l, _undo_tags_free);
143 }
144
dt_tag_new(const char * name,guint * tagid)145 gboolean dt_tag_new(const char *name, guint *tagid)
146 {
147 int rt;
148 sqlite3_stmt *stmt;
149
150 if(!name || name[0] == '\0') return FALSE; // no tagid name.
151
152 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT id FROM data.tags WHERE name = ?1", -1, &stmt,
153 NULL);
154 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
155 rt = sqlite3_step(stmt);
156 if(rt == SQLITE_ROW)
157 {
158 // tagid already exists.
159 if(tagid != NULL) *tagid = sqlite3_column_int64(stmt, 0);
160 sqlite3_finalize(stmt);
161 return TRUE;
162 }
163 sqlite3_finalize(stmt);
164
165 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "INSERT INTO data.tags (id, name) VALUES (NULL, ?1)",
166 -1, &stmt, NULL);
167 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
168 sqlite3_step(stmt);
169 sqlite3_finalize(stmt);
170
171 guint id = 0;
172 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT id FROM data.tags WHERE name = ?1", -1,
173 &stmt, NULL);
174 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
175 if(sqlite3_step(stmt) == SQLITE_ROW) id = sqlite3_column_int(stmt, 0);
176 sqlite3_finalize(stmt);
177
178 if(id && g_strstr_len(name, -1, "darktable|") == name)
179 {
180 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
181 "INSERT INTO memory.darktable_tags (tagid) VALUES (?1)",
182 -1, &stmt, NULL);
183 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, id);
184 sqlite3_step(stmt);
185 sqlite3_finalize(stmt);
186 }
187
188 if(tagid != NULL)
189 *tagid = id;
190
191 return TRUE;
192 }
193
dt_tag_new_from_gui(const char * name,guint * tagid)194 gboolean dt_tag_new_from_gui(const char *name, guint *tagid)
195 {
196 const gboolean ret = dt_tag_new(name, tagid);
197 /* if everything went fine, raise signal of tags change to refresh keywords module in GUI */
198 if(ret) DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_TAG_CHANGED);
199 return ret;
200 }
201
dt_tag_remove(const guint tagid,gboolean final)202 guint dt_tag_remove(const guint tagid, gboolean final)
203 {
204 int rv, count = -1;
205 sqlite3_stmt *stmt;
206
207 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
208 "SELECT COUNT(*) FROM main.tagged_images WHERE tagid=?1", -1, &stmt, NULL);
209 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
210 rv = sqlite3_step(stmt);
211 if(rv == SQLITE_ROW) count = sqlite3_column_int(stmt, 0);
212 sqlite3_finalize(stmt);
213
214 if(final == TRUE)
215 {
216 // let's actually remove the tag
217 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "DELETE FROM data.tags WHERE id=?1",
218 -1, &stmt, NULL);
219 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
220 sqlite3_step(stmt);
221 sqlite3_finalize(stmt);
222
223 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "DELETE FROM main.tagged_images WHERE tagid=?1",
224 -1, &stmt, NULL);
225 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
226 sqlite3_step(stmt);
227 sqlite3_finalize(stmt);
228
229 // remove it also form darktable tags table if it is there
230 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "DELETE FROM memory.darktable_tags WHERE tagid=?1",
231 -1, &stmt, NULL);
232 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
233 sqlite3_step(stmt);
234 sqlite3_finalize(stmt);
235 }
236
237 return count;
238 }
239
dt_tag_delete_tag_batch(const char * flatlist)240 void dt_tag_delete_tag_batch(const char *flatlist)
241 {
242 sqlite3_stmt *stmt;
243
244 gchar *query = g_strdup_printf("DELETE FROM data.tags WHERE id IN (%s)", flatlist);
245 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
246 sqlite3_step(stmt);
247 sqlite3_finalize(stmt);
248 g_free(query);
249
250 query = g_strdup_printf("DELETE FROM main.tagged_images WHERE tagid IN (%s)", flatlist);
251 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
252 sqlite3_step(stmt);
253 sqlite3_finalize(stmt);
254 g_free(query);
255
256 // make sure the darktable tags table is up to date
257 dt_set_darktable_tags();
258 }
259
dt_tag_remove_list(GList * tag_list)260 guint dt_tag_remove_list(GList *tag_list)
261 {
262 if (!tag_list) return 0;
263
264 char *flatlist = NULL;
265 guint count = 0;
266 guint tcount = 0;
267 for (GList *taglist = tag_list; taglist ; taglist = g_list_next(taglist))
268 {
269 const guint tagid = ((dt_tag_t *)taglist->data)->id;
270 flatlist = dt_util_dstrcat(flatlist, "%u,", tagid);
271 count++;
272 if(flatlist && count > 1000)
273 {
274 flatlist[strlen(flatlist)-1] = '\0';
275 dt_tag_delete_tag_batch(flatlist);
276 g_free(flatlist);
277 flatlist = NULL;
278 tcount = tcount + count;
279 count = 0;
280 }
281 }
282 if(flatlist)
283 {
284 flatlist[strlen(flatlist)-1] = '\0';
285 dt_tag_delete_tag_batch(flatlist);
286 g_free(flatlist);
287 tcount = tcount + count;
288 }
289 return tcount;
290 }
291
dt_tag_get_name(const guint tagid)292 gchar *dt_tag_get_name(const guint tagid)
293 {
294 int rt;
295 char *name = NULL;
296 sqlite3_stmt *stmt;
297 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT name FROM data.tags WHERE id= ?1", -1, &stmt,
298 NULL);
299 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
300 rt = sqlite3_step(stmt);
301 if(rt == SQLITE_ROW) name = g_strdup((const char *)sqlite3_column_text(stmt, 0));
302 sqlite3_finalize(stmt);
303
304 return name;
305 }
306
dt_tag_rename(const guint tagid,const gchar * new_tagname)307 void dt_tag_rename(const guint tagid, const gchar *new_tagname)
308 {
309 sqlite3_stmt *stmt;
310
311 if(!new_tagname || !new_tagname[0]) return;
312 if(dt_tag_exists(new_tagname, NULL)) return;
313
314 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
315 "UPDATE data.tags SET name = ?2 WHERE id = ?1", -1, &stmt, NULL);
316 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
317 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, new_tagname, -1, SQLITE_TRANSIENT);
318 sqlite3_step(stmt);
319 sqlite3_finalize(stmt);
320
321 }
322
dt_tag_exists(const char * name,guint * tagid)323 gboolean dt_tag_exists(const char *name, guint *tagid)
324 {
325 int rt;
326 sqlite3_stmt *stmt;
327 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "SELECT id FROM data.tags WHERE name = ?1", -1, &stmt,
328 NULL);
329 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
330 rt = sqlite3_step(stmt);
331
332 if(rt == SQLITE_ROW)
333 {
334 if(tagid != NULL) *tagid = sqlite3_column_int64(stmt, 0);
335 sqlite3_finalize(stmt);
336 return TRUE;
337 }
338
339 if(tagid != NULL) *tagid = -1;
340 sqlite3_finalize(stmt);
341 return FALSE;
342 }
343
_tag_add_tags_to_list(GList ** list,const GList * tags)344 static gboolean _tag_add_tags_to_list(GList **list, const GList *tags)
345 {
346 gboolean res = FALSE;
347 for(const GList *t = tags; t; t = g_list_next(t))
348 {
349 if(!g_list_find(*list, t->data))
350 {
351 *list = g_list_prepend(*list, t->data);
352 res = TRUE;
353 }
354 }
355 return res;
356 }
357
_tag_remove_tags_from_list(GList ** list,const GList * tags)358 static gboolean _tag_remove_tags_from_list(GList **list, const GList *tags)
359 {
360 const int nb_ini = g_list_length(*list);
361 for(const GList *t = tags; t; t = g_list_next(t))
362 {
363 *list = g_list_remove(*list, t->data);
364 }
365 return (g_list_length(*list) != nb_ini);
366 }
367
368 typedef enum dt_tag_type_t
369 {
370 DT_TAG_TYPE_DT,
371 DT_TAG_TYPE_USER,
372 DT_TAG_TYPE_ALL,
373 } dt_tag_type_t;
374
375 typedef enum dt_tag_actions_t
376 {
377 DT_TA_ATTACH = 0,
378 DT_TA_DETACH,
379 DT_TA_SET,
380 DT_TA_SET_ALL,
381 } dt_tag_actions_t;
382
383 static GList *_tag_get_tags(const gint imgid, const dt_tag_type_t type);
384
_tag_execute(const GList * tags,const GList * imgs,GList ** undo,const gboolean undo_on,const gint action)385 static gboolean _tag_execute(const GList *tags, const GList *imgs, GList **undo, const gboolean undo_on,
386 const gint action)
387 {
388 gboolean res = FALSE;
389 for(const GList *images = imgs; images; images = g_list_next(images))
390 {
391 const int image_id = GPOINTER_TO_INT(images->data);
392 dt_undo_tags_t *undotags = (dt_undo_tags_t *)malloc(sizeof(dt_undo_tags_t));
393 undotags->imgid = image_id;
394 undotags->before = _tag_get_tags(image_id, DT_TAG_TYPE_ALL);
395 switch(action)
396 {
397 case DT_TA_ATTACH:
398 undotags->after = g_list_copy(undotags->before);
399 if(_tag_add_tags_to_list(&undotags->after, tags)) res = TRUE;
400 break;
401 case DT_TA_DETACH:
402 undotags->after = g_list_copy(undotags->before);
403 if(_tag_remove_tags_from_list(&undotags->after, tags)) res = TRUE;
404 break;
405 case DT_TA_SET:
406 undotags->after = g_list_copy((GList *)tags);
407 // preserve dt tags
408 GList *dttags = _tag_get_tags(image_id, DT_TAG_TYPE_DT);
409 if(dttags) undotags->after = g_list_concat(undotags->after, dttags);
410 res = TRUE;
411 break;
412 case DT_TA_SET_ALL:
413 undotags->after = g_list_copy((GList *)tags);
414 res = TRUE;
415 break;
416 default:
417 undotags->after = g_list_copy(undotags->before);
418 res = FALSE;
419 break;
420 }
421 _pop_undo_execute(image_id, undotags->before, undotags->after);
422 if(undo_on)
423 *undo = g_list_append(*undo, undotags);
424 else
425 _undo_tags_free(undotags);
426 }
427 return res;
428 }
429
dt_tag_attach_images(const guint tagid,const GList * img,const gboolean undo_on)430 gboolean dt_tag_attach_images(const guint tagid, const GList *img, const gboolean undo_on)
431 {
432 if(!img) return FALSE;
433 GList *undo = NULL;
434 GList *tags = NULL;
435
436 if(img)
437 {
438 tags = g_list_prepend(tags, GINT_TO_POINTER(tagid));
439 if(undo_on) dt_undo_start_group(darktable.undo, DT_UNDO_TAGS);
440
441 const gboolean res = _tag_execute(tags, img, &undo, undo_on, DT_TA_ATTACH);
442
443 g_list_free(tags);
444 if(undo_on)
445 {
446 dt_undo_record(darktable.undo, NULL, DT_UNDO_TAGS, undo, _pop_undo, _tags_undo_data_free);
447 dt_undo_end_group(darktable.undo);
448 }
449
450 return res;
451 }
452
453 return FALSE;
454 }
455
dt_tag_attach(const guint tagid,const gint imgid,const gboolean undo_on,const gboolean group_on)456 gboolean dt_tag_attach(const guint tagid, const gint imgid, const gboolean undo_on, const gboolean group_on)
457 {
458 GList *imgs = NULL;
459 gboolean res = FALSE;
460 if(imgid == -1)
461 {
462 imgs = (GList *)dt_view_get_images_to_act_on(!group_on, TRUE, FALSE);
463 res = dt_tag_attach_images(tagid, imgs, undo_on);
464 }
465 else
466 {
467 if(dt_is_tag_attached(tagid, imgid)) return FALSE;
468 imgs = g_list_append(imgs, GINT_TO_POINTER(imgid));
469 res = dt_tag_attach_images(tagid, imgs, undo_on);
470 g_list_free(imgs);
471 }
472 return res;
473 }
474
dt_tag_set_tags(const GList * tags,const GList * img,const gboolean ignore_dt_tags,const gboolean clear_on,const gboolean undo_on)475 gboolean dt_tag_set_tags(const GList *tags, const GList *img, const gboolean ignore_dt_tags,
476 const gboolean clear_on, const gboolean undo_on)
477 {
478 if(img)
479 {
480 GList *undo = NULL;
481 if(undo_on) dt_undo_start_group(darktable.undo, DT_UNDO_TAGS);
482
483 const gboolean res = _tag_execute(tags, img, &undo, undo_on,
484 clear_on ? ignore_dt_tags ? DT_TA_SET : DT_TA_SET_ALL : DT_TA_ATTACH);
485 if(undo_on)
486 {
487 dt_undo_record(darktable.undo, NULL, DT_UNDO_TAGS, undo, _pop_undo, _tags_undo_data_free);
488 dt_undo_end_group(darktable.undo);
489 }
490 return res;
491 }
492 return FALSE;
493 }
494
dt_tag_attach_string_list(const gchar * tags,const GList * img,const gboolean undo_on)495 gboolean dt_tag_attach_string_list(const gchar *tags, const GList *img, const gboolean undo_on)
496 {
497 // tags may not exist yet
498 // undo only undoes the tags attachments. it doesn't remove created tags.
499 gchar **tokens = g_strsplit(tags, ",", 0);
500 gboolean res = FALSE;
501 if(tokens)
502 {
503 // tag(s) creation
504 GList *tagl = NULL;
505 gchar **entry = tokens;
506 while(*entry)
507 {
508 char *e = g_strstrip(*entry);
509 if(*e)
510 {
511 guint tagid = 0;
512 dt_tag_new(e, &tagid);
513 tagl = g_list_prepend(tagl, GINT_TO_POINTER(tagid));
514 }
515 entry++;
516 }
517
518 // attach newly created tags
519 if(img)
520 {
521 GList *undo = NULL;
522 if(undo_on) dt_undo_start_group(darktable.undo, DT_UNDO_TAGS);
523
524 res = _tag_execute(tagl, img, &undo, undo_on, DT_TA_ATTACH);
525
526 if(undo_on)
527 {
528 dt_undo_record(darktable.undo, NULL, DT_UNDO_TAGS, undo, _pop_undo, _tags_undo_data_free);
529 dt_undo_end_group(darktable.undo);
530 }
531 }
532 g_list_free(tagl);
533 }
534 g_strfreev(tokens);
535 return res;
536 }
537
dt_tag_detach_images(const guint tagid,const GList * img,const gboolean undo_on)538 gboolean dt_tag_detach_images(const guint tagid, const GList *img, const gboolean undo_on)
539 {
540 if(img)
541 {
542 GList *tags = NULL;
543 tags = g_list_prepend(tags, GINT_TO_POINTER(tagid));
544 GList *undo = NULL;
545 if(undo_on) dt_undo_start_group(darktable.undo, DT_UNDO_TAGS);
546
547 const gboolean res = _tag_execute(tags, img, &undo, undo_on, DT_TA_DETACH);
548
549 g_list_free(tags);
550 if(undo_on)
551 {
552 dt_undo_record(darktable.undo, NULL, DT_UNDO_TAGS, undo, _pop_undo, _tags_undo_data_free);
553 dt_undo_end_group(darktable.undo);
554 }
555 return res;
556 }
557 return FALSE;
558 }
559
dt_tag_detach(const guint tagid,const gint imgid,const gboolean undo_on,const gboolean group_on)560 gboolean dt_tag_detach(const guint tagid, const gint imgid, const gboolean undo_on, const gboolean group_on)
561 {
562 GList *imgs = NULL;
563 if(imgid == -1)
564 imgs = g_list_copy((GList *)dt_view_get_images_to_act_on(!group_on, TRUE, FALSE));
565 else
566 imgs = g_list_prepend(imgs, GINT_TO_POINTER(imgid));
567 if(group_on) dt_grouping_add_grouped_images(&imgs);
568
569 const gboolean res = dt_tag_detach_images(tagid, imgs, undo_on);
570 g_list_free(imgs);
571 return res;
572 }
573
dt_tag_detach_by_string(const char * name,const gint imgid,const gboolean undo_on,const gboolean group_on)574 gboolean dt_tag_detach_by_string(const char *name, const gint imgid, const gboolean undo_on,
575 const gboolean group_on)
576 {
577 if(!name || !name[0]) return FALSE;
578 guint tagid = 0;
579 if(!dt_tag_exists(name, &tagid)) return FALSE;
580
581 return dt_tag_detach(tagid, imgid, undo_on, group_on);
582 }
583
dt_set_darktable_tags()584 void dt_set_darktable_tags()
585 {
586 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.darktable_tags", NULL, NULL, NULL);
587
588 sqlite3_stmt *stmt;
589 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
590 "INSERT INTO memory.darktable_tags (tagid)"
591 " SELECT DISTINCT id"
592 " FROM data.tags"
593 " WHERE name LIKE 'darktable|%%'",
594 -1, &stmt, NULL);
595 sqlite3_step(stmt);
596 sqlite3_finalize(stmt);
597 }
598
dt_tag_get_attached(const gint imgid,GList ** result,const gboolean ignore_dt_tags)599 uint32_t dt_tag_get_attached(const gint imgid, GList **result, const gboolean ignore_dt_tags)
600 {
601 sqlite3_stmt *stmt;
602 uint32_t nb_selected = 0;
603 char *images = NULL;
604 if(imgid > 0)
605 {
606 images = g_strdup_printf("%d", imgid);
607 nb_selected = 1;
608 }
609 else
610 {
611 // we get the query used to retrieve the list of select images
612 images = dt_selection_get_list_query(darktable.selection, FALSE, FALSE);
613 // and we retrieve the number of image in the selection
614 gchar *query = g_strdup_printf("SELECT COUNT(*)"
615 " FROM (%s)",
616 images);
617 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
618 if(sqlite3_step(stmt) == SQLITE_ROW) nb_selected = sqlite3_column_int(stmt, 0);
619 sqlite3_finalize(stmt);
620 g_free(query);
621 }
622 uint32_t count = 0;
623 if(images)
624 {
625 gchar *query = g_strdup_printf(
626 "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms,"
627 " COUNT(DISTINCT I.imgid) AS inb"
628 " FROM main.tagged_images AS I"
629 " JOIN data.tags AS T ON T.id = I.tagid"
630 " WHERE I.imgid IN (%s)%s"
631 " GROUP BY I.tagid "
632 " ORDER by T.name",
633 images, ignore_dt_tags ? " AND T.id NOT IN memory.darktable_tags" : "");
634 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
635 g_free(images);
636
637 // Create result
638 *result = NULL;
639 while(sqlite3_step(stmt) == SQLITE_ROW)
640 {
641 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
642 t->id = sqlite3_column_int(stmt, 0);
643 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 1));
644 t->leave = g_strrstr(t->tag, "|");
645 t->leave = t->leave ? t->leave + 1 : t->tag;
646 t->flags = sqlite3_column_int(stmt, 2);
647 t->synonym = g_strdup((char *)sqlite3_column_text(stmt, 3));
648 const uint32_t imgnb = sqlite3_column_int(stmt, 4);
649 t->count = imgnb;
650 t->select = (nb_selected == 0) ? DT_TS_NO_IMAGE :
651 (imgnb == nb_selected) ? DT_TS_ALL_IMAGES :
652 (imgnb == 0) ? DT_TS_NO_IMAGE : DT_TS_SOME_IMAGES;
653 *result = g_list_append(*result, t);
654 count++;
655 }
656 sqlite3_finalize(stmt);
657 g_free(query);
658 }
659 return count;
660 }
661
_tag_get_attached_export(const gint imgid,GList ** result)662 static uint32_t _tag_get_attached_export(const gint imgid, GList **result)
663 {
664 if(!(imgid > 0)) return 0;
665
666 sqlite3_stmt *stmt;
667 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
668 "SELECT DISTINCT TI.tagid, T.name, T.flags, T.synonyms"
669 " FROM main.tagged_images AS TI"
670 " JOIN data.tags AS T ON T.id = TI.tagid"
671 " WHERE TI.imgid = ?1 AND TI.tagid NOT IN memory.darktable_tags",
672 -1, &stmt, NULL);
673 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
674 // Create result
675 uint32_t count = 0;
676 while(sqlite3_step(stmt) == SQLITE_ROW)
677 {
678 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
679 t->id = sqlite3_column_int(stmt, 0);
680 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 1));
681 t->leave = g_strrstr(t->tag, "|");
682 t->leave = t->leave ? t->leave + 1 : t->tag;
683 t->flags = sqlite3_column_int(stmt, 2);
684 t->synonym = g_strdup((char *)sqlite3_column_text(stmt, 3));
685 *result = g_list_append(*result, t);
686 count++;
687 }
688 sqlite3_finalize(stmt);
689
690 return count;
691 }
692
sort_tag_by_path(gconstpointer a,gconstpointer b)693 static gint sort_tag_by_path(gconstpointer a, gconstpointer b)
694 {
695 const dt_tag_t *tuple_a = (const dt_tag_t *)a;
696 const dt_tag_t *tuple_b = (const dt_tag_t *)b;
697
698 return g_strcmp0(tuple_a->tag, tuple_b->tag);
699 }
700
sort_tag_by_leave(gconstpointer a,gconstpointer b)701 static gint sort_tag_by_leave(gconstpointer a, gconstpointer b)
702 {
703 const dt_tag_t *tuple_a = (const dt_tag_t *)a;
704 const dt_tag_t *tuple_b = (const dt_tag_t *)b;
705
706 return g_strcmp0(tuple_a->leave, tuple_b->leave);
707 }
708
sort_tag_by_count(gconstpointer a,gconstpointer b)709 static gint sort_tag_by_count(gconstpointer a, gconstpointer b)
710 {
711 const dt_tag_t *tuple_a = (const dt_tag_t *)a;
712 const dt_tag_t *tuple_b = (const dt_tag_t *)b;
713
714 return (tuple_b->count - tuple_a->count);
715 }
716 // sort_type 0 = path, 1 = leave other = count
dt_sort_tag(GList * tags,gint sort_type)717 GList *dt_sort_tag(GList *tags, gint sort_type)
718 {
719 GList *sorted_tags;
720 if (sort_type <= 1)
721 {
722 for(GList *taglist = tags; taglist; taglist = g_list_next(taglist))
723 {
724 // order such that sub tags are coming directly behind their parent
725 gchar *tag = ((dt_tag_t *)taglist->data)->tag;
726 for(char *letter = tag; *letter; letter++)
727 if(*letter == '|') *letter = '\1';
728 }
729 sorted_tags = g_list_sort(tags, !sort_type ? sort_tag_by_path : sort_tag_by_leave);
730 for(GList *taglist = sorted_tags; taglist; taglist = g_list_next(taglist))
731 {
732 gchar *tag = ((dt_tag_t *)taglist->data)->tag;
733 for(char *letter = tag; *letter; letter++)
734 if(*letter == '\1') *letter = '|';
735 }
736 }
737 else
738 {
739 sorted_tags = g_list_sort(tags, sort_tag_by_count);
740 }
741 return sorted_tags;
742 }
743
dt_tag_get_list(gint imgid)744 GList *dt_tag_get_list(gint imgid)
745 {
746 GList *taglist = NULL;
747 GList *tags = NULL;
748
749 gboolean omit_tag_hierarchy = dt_conf_get_bool("omit_tag_hierarchy");
750
751 uint32_t count = dt_tag_get_attached(imgid, &taglist, TRUE);
752
753 if(count < 1) return NULL;
754
755 for(; taglist; taglist = g_list_next(taglist))
756 {
757 dt_tag_t *t = (dt_tag_t *)taglist->data;
758 gchar *value = t->tag;
759
760 gchar **pch = g_strsplit(value, "|", -1);
761
762 if(pch != NULL)
763 {
764 if(omit_tag_hierarchy)
765 {
766 char **iter = pch;
767 for(; *iter && *(iter + 1); iter++);
768 if(*iter) tags = g_list_prepend(tags, g_strdup(*iter));
769 }
770 else
771 {
772 size_t j = 0;
773 while(pch[j] != NULL)
774 {
775 tags = g_list_prepend(tags, g_strdup(pch[j]));
776 j++;
777 }
778 }
779 g_strfreev(pch);
780 }
781 }
782
783 dt_tag_free_result(&taglist);
784
785 return dt_util_glist_uniq(tags);
786 }
787
dt_tag_get_hierarchical(gint imgid)788 GList *dt_tag_get_hierarchical(gint imgid)
789 {
790 GList *taglist = NULL;
791 GList *tags = NULL;
792
793 int count = dt_tag_get_attached(imgid, &taglist, TRUE);
794
795 if(count < 1) return NULL;
796
797 for(GList *tag_iter = taglist; tag_iter; tag_iter = g_list_next(tag_iter))
798 {
799 dt_tag_t *t = (dt_tag_t *)tag_iter->data;
800 tags = g_list_prepend(tags, g_strdup(t->tag));
801 }
802
803 dt_tag_free_result(&taglist);
804
805 tags = g_list_reverse(tags); // list was built in reverse order, so un-reverse it
806 return tags;
807 }
808
_tag_get_tags(const gint imgid,const dt_tag_type_t type)809 static GList *_tag_get_tags(const gint imgid, const dt_tag_type_t type)
810 {
811 GList *tags = NULL;
812 char *images = NULL;
813 if(imgid > 0)
814 images = g_strdup_printf("%d", imgid);
815 else
816 {
817 // we get the query used to retrieve the list of select images
818 images = dt_selection_get_list_query(darktable.selection, FALSE, FALSE);
819 }
820
821 sqlite3_stmt *stmt;
822 char query[256] = { 0 };
823 snprintf(query, sizeof(query), "SELECT DISTINCT T.id"
824 " FROM main.tagged_images AS I"
825 " JOIN data.tags T on T.id = I.tagid"
826 " WHERE I.imgid IN (%s) %s",
827 images, type == DT_TAG_TYPE_ALL ? "" :
828 type == DT_TAG_TYPE_DT ? "AND T.id IN memory.darktable_tags" :
829 "AND NOT T.id IN memory.darktable_tags");
830 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
831
832 while(sqlite3_step(stmt) == SQLITE_ROW)
833 {
834 tags = g_list_prepend(tags, GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
835 }
836
837 sqlite3_finalize(stmt);
838 g_free(images);
839 return tags;
840 }
841
dt_tag_get_tags(const gint imgid,const gboolean ignore_dt_tags)842 GList *dt_tag_get_tags(const gint imgid, const gboolean ignore_dt_tags)
843 {
844 return _tag_get_tags(imgid, ignore_dt_tags ? DT_TAG_TYPE_USER : DT_TAG_TYPE_ALL);
845 }
846
_is_not_exportable_tag(gconstpointer a,gconstpointer b)847 static gint _is_not_exportable_tag(gconstpointer a, gconstpointer b)
848 {
849 dt_tag_t *ta = (dt_tag_t *)a;
850 dt_tag_t *tb = (dt_tag_t *)b;
851 return ((g_strcmp0(ta->tag, tb->tag) == 0) &&
852 ((ta->flags) & (DT_TF_CATEGORY | DT_TF_PRIVATE))) ? 0 : -1;
853 }
854
dt_tag_get_list_export(gint imgid,int32_t flags)855 GList *dt_tag_get_list_export(gint imgid, int32_t flags)
856 {
857 GList *taglist = NULL;
858 GList *tags = NULL;
859
860 gboolean omit_tag_hierarchy = flags & DT_META_OMIT_HIERARCHY;
861 gboolean export_private_tags = flags & DT_META_PRIVATE_TAG;
862 gboolean export_tag_synonyms = flags & DT_META_SYNONYMS_TAG;
863
864 uint32_t count = _tag_get_attached_export(imgid, &taglist);
865
866 if(count < 1) return NULL;
867 GList *sorted_tags = dt_sort_tag(taglist, 0);
868 sorted_tags = g_list_reverse(sorted_tags);
869
870 // reset private if export private
871 if(export_private_tags)
872 {
873 for(GList *tagt = sorted_tags; tagt; tagt = g_list_next(tagt))
874 {
875 dt_tag_t *t = (dt_tag_t *)sorted_tags->data;
876 t->flags &= ~DT_TF_PRIVATE;
877 }
878 }
879 for(; sorted_tags; sorted_tags = g_list_next(sorted_tags))
880 {
881 dt_tag_t *t = (dt_tag_t *)sorted_tags->data;
882 if ((export_private_tags || !(t->flags & DT_TF_PRIVATE))
883 && !(t->flags & DT_TF_CATEGORY))
884 {
885 gchar *tagname = t->leave;
886 tags = g_list_prepend(tags, g_strdup(tagname));
887
888 // if not "omit tag hierarchy" the path elements are added
889 // unless otherwise stated (defined as category or private)
890 if(!omit_tag_hierarchy)
891 {
892 GList *next = g_list_next(sorted_tags);
893 gchar *end = g_strrstr(t->tag, "|");
894 while (end)
895 {
896 end[0] = '\0';
897 end = g_strrstr(t->tag, "|");
898 if (!next ||
899 !g_list_find_custom(next, t, (GCompareFunc)_is_not_exportable_tag))
900 {
901 const gchar *tag = end ? end + 1 : t->tag;
902 tags = g_list_prepend(tags, g_strdup(tag));
903 }
904 }
905 }
906
907 // add synonyms as necessary
908 if (export_tag_synonyms)
909 {
910 gchar *synonyms = t->synonym;
911 if (synonyms && synonyms[0])
912 {
913 gchar **tokens = g_strsplit(synonyms, ",", 0);
914 if(tokens)
915 {
916 gchar **entry = tokens;
917 while(*entry)
918 {
919 char *e = *entry;
920 if (*e == ' ') e++;
921 tags = g_list_append(tags, g_strdup(e));
922 entry++;
923 }
924 }
925 g_strfreev(tokens);
926 }
927 }
928 }
929 }
930 dt_tag_free_result(&taglist);
931
932 return dt_util_glist_uniq(tags);
933 }
934
dt_tag_get_hierarchical_export(gint imgid,int32_t flags)935 GList *dt_tag_get_hierarchical_export(gint imgid, int32_t flags)
936 {
937 GList *taglist = NULL;
938 GList *tags = NULL;
939
940 const int count = dt_tag_get_attached(imgid, &taglist, TRUE);
941
942 if(count < 1) return NULL;
943 const gboolean export_private_tags = flags & DT_META_PRIVATE_TAG;
944
945 for(GList *tag_iter = taglist; tag_iter; tag_iter = g_list_next(tag_iter))
946 {
947 dt_tag_t *t = (dt_tag_t *)tag_iter->data;
948 if (export_private_tags || !(t->flags & DT_TF_PRIVATE))
949 {
950 tags = g_list_prepend(tags, g_strdup(t->tag));
951 }
952 }
953
954 dt_tag_free_result(&taglist);
955
956 return g_list_reverse(tags); // list was built in reverse order, so un-reverse it
957 }
958
dt_is_tag_attached(const guint tagid,const gint imgid)959 gboolean dt_is_tag_attached(const guint tagid, const gint imgid)
960 {
961 sqlite3_stmt *stmt;
962
963 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
964 "SELECT imgid"
965 " FROM main.tagged_images"
966 " WHERE imgid = ?1 AND tagid = ?2", -1, &stmt, NULL);
967 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
968 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, tagid);
969
970 const gboolean ret = (sqlite3_step(stmt) == SQLITE_ROW);
971 sqlite3_finalize(stmt);
972 return ret;
973 }
974
dt_tag_get_images(const gint tagid)975 GList *dt_tag_get_images(const gint tagid)
976 {
977 GList *result = NULL;
978 sqlite3_stmt *stmt;
979
980 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
981 "SELECT imgid FROM main.tagged_images"
982 " WHERE tagid = ?1",
983 -1, &stmt, NULL);
984 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
985
986 while(sqlite3_step(stmt) == SQLITE_ROW)
987 {
988 int id = sqlite3_column_int(stmt, 0);
989 result = g_list_prepend(result, GINT_TO_POINTER(id));
990 }
991 sqlite3_finalize(stmt);
992
993 return g_list_reverse(result); // list was built in reverse order, so un-reverse it
994 }
995
dt_tag_get_images_from_list(const GList * img,const gint tagid)996 GList *dt_tag_get_images_from_list(const GList *img, const gint tagid)
997 {
998 GList *result = NULL;
999 char *images = NULL;
1000 for(GList *imgs = (GList *)img; imgs; imgs = g_list_next(imgs))
1001 {
1002 images = dt_util_dstrcat(images, "%d,",GPOINTER_TO_INT(imgs->data));
1003 }
1004 if(images)
1005 {
1006 images[strlen(images) - 1] = '\0';
1007
1008 sqlite3_stmt *stmt;
1009
1010 gchar *query = g_strdup_printf(
1011 "SELECT imgid FROM main.tagged_images"
1012 " WHERE tagid = %d AND imgid IN (%s)",
1013 tagid, images);
1014 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1015
1016 while(sqlite3_step(stmt) == SQLITE_ROW)
1017 {
1018 int id = sqlite3_column_int(stmt, 0);
1019 result = g_list_prepend(result, GINT_TO_POINTER(id));
1020 }
1021
1022 sqlite3_finalize(stmt);
1023 g_free(query);
1024 g_free(images);
1025 }
1026 return g_list_reverse(result); // list was built in reverse order, so un-reverse it
1027 }
1028
dt_tag_get_suggestions(GList ** result)1029 uint32_t dt_tag_get_suggestions(GList **result)
1030 {
1031 sqlite3_stmt *stmt;
1032
1033 /* list tags and count */
1034 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1035 "INSERT INTO memory.taglist (id, count)"
1036 " SELECT S.tagid, COUNT(*)"
1037 " FROM main.tagged_images AS S"
1038 " WHERE S.tagid NOT IN memory.darktable_tags"
1039 " GROUP BY S.tagid",
1040 -1, &stmt, NULL);
1041 sqlite3_step(stmt);
1042 sqlite3_finalize(stmt);
1043
1044 const uint32_t nb_selected = dt_selected_images_count();
1045 /* Now put all the bits together */
1046 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1047 "SELECT T.name, T.id, MT.count, CT.imgnb, T.flags, T.synonyms"
1048 " FROM memory.taglist MT"
1049 " JOIN data.tags T ON MT.id = T.id"
1050 " LEFT JOIN (SELECT tagid, COUNT(DISTINCT imgid) AS imgnb"
1051 " FROM main.tagged_images"
1052 " WHERE imgid IN (SELECT imgid FROM main.selected_images)"
1053 " GROUP BY tagid) AS CT"
1054 " ON CT.tagid = MT.id"
1055 " WHERE T.id NOT IN (SELECT DISTINCT tagid"
1056 " FROM (SELECT TI.tagid, COUNT(DISTINCT SI.imgid) AS imgnb"
1057 " FROM main.selected_images AS SI"
1058 " JOIN main.tagged_images AS TI ON TI.imgid = SI.imgid"
1059 " GROUP BY TI.tagid)"
1060 " WHERE imgnb = ?1)"
1061 " AND (T.flags IS NULL OR (T.flags & 1) = 0)"
1062 " ORDER BY MT.count DESC"
1063 " LIMIT 500",
1064 -1, &stmt, NULL);
1065 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, nb_selected);
1066
1067 /* ... and create the result list to send upwards */
1068 uint32_t count = 0;
1069 while(sqlite3_step(stmt) == SQLITE_ROW)
1070 {
1071 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
1072 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 0));
1073 t->leave = g_strrstr(t->tag, "|");
1074 t->leave = t->leave ? t->leave + 1 : t->tag;
1075 t->id = sqlite3_column_int(stmt, 1);
1076 t->count = sqlite3_column_int(stmt, 2);
1077 const uint32_t imgnb = sqlite3_column_int(stmt, 3);
1078 t->select = (nb_selected == 0) ? DT_TS_NO_IMAGE :
1079 (imgnb == nb_selected) ? DT_TS_ALL_IMAGES :
1080 (imgnb == 0) ? DT_TS_NO_IMAGE : DT_TS_SOME_IMAGES;
1081 t->flags = sqlite3_column_int(stmt, 4);
1082 t->synonym = g_strdup((char *)sqlite3_column_text(stmt, 5));
1083 *result = g_list_append(*result, t);
1084 count++;
1085 }
1086
1087 sqlite3_finalize(stmt);
1088 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.taglist", NULL, NULL, NULL);
1089
1090 return count;
1091 }
1092
dt_tag_count_tags_images(const gchar * keyword,int * tag_count,int * img_count)1093 void dt_tag_count_tags_images(const gchar *keyword, int *tag_count, int *img_count)
1094 {
1095 sqlite3_stmt *stmt;
1096 *tag_count = 0;
1097 *img_count = 0;
1098
1099 if(!keyword) return;
1100 gchar *keyword_expr = g_strdup_printf("%s|", keyword);
1101
1102 /* Only select tags that are equal or child to the one we are looking for once. */
1103 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1104 "INSERT INTO memory.similar_tags (tagid)"
1105 " SELECT id"
1106 " FROM data.tags"
1107 " WHERE name = ?1 OR SUBSTR(name, 1, LENGTH(?2)) = ?2",
1108 -1, &stmt, NULL);
1109 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, keyword, -1, SQLITE_TRANSIENT);
1110 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, keyword_expr, -1, SQLITE_TRANSIENT);
1111 sqlite3_step(stmt);
1112 sqlite3_finalize(stmt);
1113
1114 g_free(keyword_expr);
1115
1116 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1117 "SELECT COUNT(DISTINCT tagid) FROM memory.similar_tags",
1118 -1, &stmt, NULL);
1119 sqlite3_step(stmt);
1120 *tag_count = sqlite3_column_int(stmt, 0);
1121 sqlite3_finalize(stmt);
1122
1123 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1124 "SELECT COUNT(DISTINCT ti.imgid)"
1125 " FROM main.tagged_images AS ti "
1126 " JOIN memory.similar_tags AS st"
1127 " ON st.tagid = ti.tagid",
1128 -1, &stmt, NULL);
1129
1130 sqlite3_step(stmt);
1131 *img_count = sqlite3_column_int(stmt, 0);
1132 sqlite3_finalize(stmt);
1133
1134 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.similar_tags", NULL, NULL, NULL);
1135 }
1136
dt_tag_get_tags_images(const gchar * keyword,GList ** tag_list,GList ** img_list)1137 void dt_tag_get_tags_images(const gchar *keyword, GList **tag_list, GList **img_list)
1138 {
1139 sqlite3_stmt *stmt;
1140
1141 if(!keyword) return;
1142 gchar *keyword_expr = g_strdup_printf("%s|", keyword);
1143
1144 /* Only select tags that are equal or child to the one we are looking for once. */
1145 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1146 "INSERT INTO memory.similar_tags (tagid)"
1147 " SELECT id"
1148 " FROM data.tags"
1149 " WHERE name = ?1 OR SUBSTR(name, 1, LENGTH(?2)) = ?2",
1150 -1, &stmt, NULL);
1151 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, keyword, -1, SQLITE_TRANSIENT);
1152 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, keyword_expr, -1, SQLITE_TRANSIENT);
1153 sqlite3_step(stmt);
1154 sqlite3_finalize(stmt);
1155
1156 g_free(keyword_expr);
1157
1158 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1159 "SELECT ST.tagid, T.name"
1160 " FROM memory.similar_tags ST"
1161 " JOIN data.tags T"
1162 " ON T.id = ST.tagid ",
1163 -1, &stmt, NULL);
1164
1165 while(sqlite3_step(stmt) == SQLITE_ROW)
1166 {
1167 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
1168 t->id = sqlite3_column_int(stmt, 0);
1169 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 1));
1170 *tag_list = g_list_append((*tag_list), t);
1171 }
1172 sqlite3_finalize(stmt);
1173
1174 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1175 "SELECT DISTINCT ti.imgid"
1176 " FROM main.tagged_images AS ti"
1177 " JOIN memory.similar_tags AS st"
1178 " ON st.tagid = ti.tagid",
1179 -1, &stmt, NULL);
1180 while(sqlite3_step(stmt) == SQLITE_ROW)
1181 {
1182 *img_list = g_list_append((*img_list), GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
1183 }
1184 sqlite3_finalize(stmt);
1185
1186 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.similar_tags", NULL, NULL, NULL);
1187 }
1188
dt_selected_images_count()1189 uint32_t dt_selected_images_count()
1190 {
1191 sqlite3_stmt *stmt;
1192
1193 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1194 "SELECT count(*) FROM main.selected_images",
1195 -1, &stmt, NULL);
1196 sqlite3_step(stmt);
1197 const uint32_t nb_selected = sqlite3_column_int(stmt, 0);
1198 sqlite3_finalize(stmt);
1199 return nb_selected;
1200 }
1201
dt_tag_images_count(gint tagid)1202 uint32_t dt_tag_images_count(gint tagid)
1203 {
1204 sqlite3_stmt *stmt;
1205
1206 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1207 "SELECT COUNT(DISTINCT imgid) AS imgnb"
1208 " FROM main.tagged_images"
1209 " WHERE tagid = ?1",
1210 -1, &stmt, NULL);
1211 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1212 sqlite3_step(stmt);
1213 const uint32_t nb_images = sqlite3_column_int(stmt, 0);
1214 sqlite3_finalize(stmt);
1215 return nb_images;
1216 }
1217
dt_tag_get_with_usage(GList ** result)1218 uint32_t dt_tag_get_with_usage(GList **result)
1219 {
1220 sqlite3_stmt *stmt;
1221
1222 /* Select tags that are similar to the keyword and are actually used to tag images*/
1223 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1224 "INSERT INTO memory.taglist (id, count)"
1225 " SELECT tagid, COUNT(*)"
1226 " FROM main.tagged_images"
1227 " GROUP BY tagid",
1228 -1, &stmt, NULL);
1229 sqlite3_step(stmt);
1230 sqlite3_finalize(stmt);
1231
1232 const uint32_t nb_selected = dt_selected_images_count();
1233
1234 /* Now put all the bits together */
1235 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1236 "SELECT T.name, T.id, MT.count, CT.imgnb, T.flags, T.synonyms"
1237 " FROM data.tags T "
1238 " LEFT JOIN memory.taglist MT ON MT.id = T.id "
1239 " LEFT JOIN (SELECT tagid, COUNT(DISTINCT imgid) AS imgnb"
1240 " FROM main.tagged_images "
1241 " WHERE imgid IN (SELECT imgid FROM main.selected_images) GROUP BY tagid) AS CT "
1242 " ON CT.tagid = T.id"
1243 " WHERE T.id NOT IN memory.darktable_tags "
1244 " ORDER BY T.name ",
1245 -1, &stmt, NULL);
1246
1247 /* ... and create the result list to send upwards */
1248 uint32_t count = 0;
1249 while(sqlite3_step(stmt) == SQLITE_ROW)
1250 {
1251 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
1252 t->tag = g_strdup((char *)sqlite3_column_text(stmt, 0));
1253 t->leave = g_strrstr(t->tag, "|");
1254 t->leave = t->leave ? t->leave + 1 : t->tag;
1255 t->id = sqlite3_column_int(stmt, 1);
1256 t->count = sqlite3_column_int(stmt, 2);
1257 const uint32_t imgnb = sqlite3_column_int(stmt, 3);
1258 t->select = (nb_selected == 0) ? DT_TS_NO_IMAGE :
1259 (imgnb == nb_selected) ? DT_TS_ALL_IMAGES :
1260 (imgnb == 0) ? DT_TS_NO_IMAGE : DT_TS_SOME_IMAGES;
1261 t->flags = sqlite3_column_int(stmt, 4);
1262 t->synonym = g_strdup((char *)sqlite3_column_text(stmt, 5));
1263 *result = g_list_append(*result, t);
1264 count++;
1265 }
1266
1267 sqlite3_finalize(stmt);
1268 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.taglist", NULL, NULL, NULL);
1269
1270 return count;
1271 }
1272
dt_cleanup_synonyms(gchar * synonyms_entry)1273 static gchar *dt_cleanup_synonyms(gchar *synonyms_entry)
1274 {
1275 gchar *synonyms = NULL;
1276 for(char *letter = synonyms_entry; *letter; letter++)
1277 {
1278 if(*letter == ';' || *letter == '\n') *letter = ',';
1279 if(*letter == '\r') *letter = ' ';
1280 }
1281 gchar **tokens = g_strsplit(synonyms_entry, ",", 0);
1282 if(tokens)
1283 {
1284 gchar **entry = tokens;
1285 while (*entry)
1286 {
1287 char *e = g_strstrip(*entry);
1288 if(*e)
1289 {
1290 synonyms = dt_util_dstrcat(synonyms, "%s, ", e);
1291 }
1292 entry++;
1293 }
1294 if (synonyms)
1295 synonyms[strlen(synonyms) - 2] = '\0';
1296 }
1297 g_strfreev(tokens);
1298 return synonyms;
1299 }
1300
dt_tag_get_synonyms(gint tagid)1301 gchar *dt_tag_get_synonyms(gint tagid)
1302 {
1303 sqlite3_stmt *stmt;
1304 gchar *synonyms = NULL;
1305
1306 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1307 "SELECT synonyms FROM data.tags WHERE id = ?1 ",
1308 -1, &stmt, NULL);
1309 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1310
1311 if (sqlite3_step(stmt) == SQLITE_ROW)
1312 {
1313 synonyms = g_strdup((char *)sqlite3_column_text(stmt, 0));
1314 }
1315 sqlite3_finalize(stmt);
1316 return synonyms;
1317 }
1318
dt_tag_set_synonyms(gint tagid,gchar * synonyms_entry)1319 void dt_tag_set_synonyms(gint tagid, gchar *synonyms_entry)
1320 {
1321 if (!synonyms_entry) return;
1322 char *synonyms = dt_cleanup_synonyms(synonyms_entry);
1323
1324 sqlite3_stmt *stmt;
1325 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1326 "UPDATE data.tags SET synonyms = ?2 WHERE id = ?1 ",
1327 -1, &stmt, NULL);
1328 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1329 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, synonyms, -1, SQLITE_TRANSIENT);
1330 sqlite3_step(stmt);
1331 sqlite3_finalize(stmt);
1332 g_free(synonyms);
1333 }
1334
dt_tag_get_flags(gint tagid)1335 gint dt_tag_get_flags(gint tagid)
1336 {
1337 sqlite3_stmt *stmt;
1338
1339 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1340 "SELECT flags FROM data.tags WHERE id = ?1 ",
1341 -1, &stmt, NULL);
1342 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1343
1344 gint flags = 0;
1345 if (sqlite3_step(stmt) == SQLITE_ROW)
1346 {
1347 flags = sqlite3_column_int(stmt, 0);
1348 }
1349 sqlite3_finalize(stmt);
1350 return flags;
1351 }
1352
dt_tag_set_flags(gint tagid,gint flags)1353 void dt_tag_set_flags(gint tagid, gint flags)
1354 {
1355 sqlite3_stmt *stmt;
1356
1357 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1358 "UPDATE data.tags SET flags = ?2 WHERE id = ?1 ",
1359 -1, &stmt, NULL);
1360 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1361 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, flags);
1362 sqlite3_step(stmt);
1363 sqlite3_finalize(stmt);
1364 }
1365
dt_tag_add_synonym(gint tagid,gchar * synonym)1366 void dt_tag_add_synonym(gint tagid, gchar *synonym)
1367 {
1368 char *synonyms = dt_tag_get_synonyms(tagid);
1369 if (synonyms)
1370 {
1371 synonyms = dt_util_dstrcat(synonyms, ", %s", synonym);
1372 }
1373 else
1374 {
1375 synonyms = g_strdup(synonym);
1376 }
1377 sqlite3_stmt *stmt;
1378 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1379 "UPDATE data.tags SET synonyms = ?2 WHERE id = ?1 ",
1380 -1, &stmt, NULL);
1381 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1382 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, synonyms, -1, SQLITE_TRANSIENT);
1383 sqlite3_step(stmt);
1384 sqlite3_finalize(stmt);
1385 g_free(synonyms);
1386 }
1387
_free_result_item(gpointer data)1388 static void _free_result_item(gpointer data)
1389 {
1390 dt_tag_t *t = (dt_tag_t*)data;
1391 g_free(t->tag);
1392 g_free(t->synonym);
1393 g_free(t);
1394 }
1395
dt_tag_free_result(GList ** result)1396 void dt_tag_free_result(GList **result)
1397 {
1398 if(result && *result)
1399 {
1400 g_list_free_full(*result, _free_result_item);
1401 }
1402 }
1403
dt_tag_get_recent_used(GList ** result)1404 uint32_t dt_tag_get_recent_used(GList **result)
1405 {
1406 return 0;
1407 }
1408
1409 /*
1410 TODO
1411 the file format allows to specify {synonyms} that are one hierarchy level deeper than the parent. those are not
1412 to be shown in the gui but can be searched. when the parent or a synonym is attached then ALSO the rest of the
1413 bunch is to be added.
1414 there is also a ~ prefix for tags that indicate that the tag order has to be kept instead of sorting them. that's
1415 also not possible at the moment.
1416 */
dt_tag_import(const char * filename)1417 ssize_t dt_tag_import(const char *filename)
1418 {
1419 FILE *fd = g_fopen(filename, "r");
1420 if(!fd) return -1;
1421
1422 GList * hierarchy = NULL;
1423 char *line = NULL;
1424 size_t len = 0;
1425 ssize_t count = 0;
1426 guint tagid = 0;
1427 guint previous_category_depth = 0;
1428 gboolean previous_category = FALSE;
1429 gboolean previous_synonym = FALSE;
1430
1431 while(getline(&line, &len, fd) != -1)
1432 {
1433 // remove newlines and set start past the initial tabs
1434 char *start = line;
1435 while(*start == '\t' || *start == ' ' || *start == ',' || *start == ';') start++;
1436 const int depth = start - line;
1437
1438 char *end = line + strlen(line) - 1;
1439 while((*end == '\n' || *end == '\r' || *end == ',' || *end == ';') && end >= start)
1440 {
1441 *end = '\0';
1442 end--;
1443 }
1444
1445 // remove control characters from the string
1446 // if no associated synonym the previous category node can be reused
1447 gboolean skip = FALSE;
1448 gboolean category = FALSE;
1449 gboolean synonym = FALSE;
1450 if (*start == '[' && *end == ']') // categories
1451 {
1452 category = TRUE;
1453 start++;
1454 *end-- = '\0';
1455 }
1456 else if (*start == '{' && *end == '}') // synonyms
1457 {
1458 synonym = TRUE;
1459 start++;
1460 *end-- = '\0';
1461 }
1462 if(*start == '~') // fixed order. TODO not possible with our db
1463 {
1464 skip = TRUE;
1465 start++;
1466 }
1467
1468 if (synonym)
1469 {
1470 // associate the synonym to last tag
1471 if (tagid)
1472 {
1473 char *tagname = g_strdup(start);
1474 // clear synonyms before importing the new ones => allows export, modification and back import
1475 if (!previous_synonym) dt_tag_set_synonyms(tagid, "");
1476 dt_tag_add_synonym(tagid, tagname);
1477 g_free(tagname);
1478 }
1479 }
1480 else
1481 {
1482 // remove everything past the current prefix from hierarchy
1483 GList *iter = g_list_nth(hierarchy, depth);
1484 while(iter)
1485 {
1486 GList *current = iter;
1487 iter = g_list_next(iter);
1488 hierarchy = g_list_delete_link(hierarchy, current);
1489 }
1490
1491 // add the current level
1492 hierarchy = g_list_append(hierarchy, g_strdup(start));
1493
1494 // add tag to db iff it's not something to be ignored
1495 if(!skip)
1496 {
1497 char *tag = dt_util_glist_to_str("|", hierarchy);
1498 if (previous_category && (depth > previous_category_depth + 1))
1499 {
1500 // reuse previous tag
1501 dt_tag_rename(tagid, tag);
1502 if (!category)
1503 dt_tag_set_flags(tagid, 0);
1504 }
1505 else
1506 {
1507 // create a new tag
1508 count++;
1509 tagid = 1; // if 0, dt_tag_new creates a new one even if the tag already exists
1510 dt_tag_new(tag, &tagid);
1511 if (category)
1512 dt_tag_set_flags(tagid, DT_TF_CATEGORY);
1513 }
1514 g_free(tag);
1515 }
1516 }
1517 previous_category_depth = category ? depth : 0;
1518 previous_category = category;
1519 previous_synonym = synonym;
1520 }
1521
1522 free(line);
1523 g_list_free_full(hierarchy, g_free);
1524 fclose(fd);
1525
1526 DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_TAG_CHANGED);
1527
1528 return count;
1529 }
1530
1531 /*
1532 TODO: there is one corner case where i am not sure if we are doing the correct thing. some examples i found
1533 on the internet agreed with this version, some used an alternative:
1534 consider two tags like "foo|bar" and "foo|bar|baz". the "foo|bar" part is both a regular tag (from the 1st tag)
1535 and also a category (from the 2nd tag). the two way to output are
1536
1537 [foo]
1538 bar
1539 baz
1540
1541 and
1542
1543 [foo]
1544 bar
1545 [bar]
1546 baz
1547
1548 we are using the first (mostly because it was easier to implement ;)). if this poses problems with other programs
1549 supporting these files then we should fix that.
1550 */
dt_tag_export(const char * filename)1551 ssize_t dt_tag_export(const char *filename)
1552 {
1553 FILE *fd = g_fopen(filename, "w");
1554
1555 if(!fd) return -1;
1556
1557 GList *tags = NULL;
1558 gint count = 0;
1559 dt_tag_get_with_usage(&tags);
1560 GList *sorted_tags = dt_sort_tag(tags, 0);
1561
1562 gchar **hierarchy = NULL;
1563 for(GList *tag_elt = sorted_tags; tag_elt; tag_elt = g_list_next(tag_elt))
1564 {
1565 const gchar *tag = ((dt_tag_t *)tag_elt->data)->tag;
1566 const char *synonyms = ((dt_tag_t *)tag_elt->data)->synonym;
1567 const guint flags = ((dt_tag_t *)tag_elt->data)->flags;
1568 gchar **tokens = g_strsplit(tag, "|", -1);
1569
1570 // find how many common levels are shared with the last tag
1571 int common_start;
1572 for(common_start = 0; hierarchy && hierarchy[common_start] && tokens && tokens[common_start]; common_start++)
1573 {
1574 if(g_strcmp0(hierarchy[common_start], tokens[common_start])) break;
1575 }
1576
1577 g_strfreev(hierarchy);
1578 hierarchy = tokens;
1579
1580 int tabs = common_start;
1581 for(size_t i = common_start; tokens && tokens[i]; i++, tabs++)
1582 {
1583 for(int j = 0; j < tabs; j++) fputc('\t', fd);
1584 if(!tokens[i + 1])
1585 {
1586 count++;
1587 if (flags & DT_TF_CATEGORY)
1588 fprintf(fd, "[%s]\n", tokens[i]);
1589 else
1590 fprintf(fd, "%s\n", tokens[i]);
1591 if (synonyms && synonyms[0])
1592 {
1593 gchar **tokens2 = g_strsplit(synonyms, ",", 0);
1594 if(tokens2)
1595 {
1596 gchar **entry = tokens2;
1597 while(*entry)
1598 {
1599 char *e = *entry;
1600 if (*e == ' ') e++;
1601 for(int j = 0; j < tabs+1; j++) fputc('\t', fd);
1602 fprintf(fd, "{%s}\n", e);
1603 entry++;
1604 }
1605 }
1606 g_strfreev(tokens2);
1607 }
1608 }
1609 else
1610 fprintf(fd, "%s\n", tokens[i]);
1611 }
1612 }
1613
1614 g_strfreev(hierarchy);
1615
1616 dt_tag_free_result(&tags);
1617
1618 fclose(fd);
1619
1620 return count;
1621 }
1622
dt_tag_get_subtags(const gint imgid,const char * category,const int level)1623 char *dt_tag_get_subtags(const gint imgid, const char *category, const int level)
1624 {
1625 if (!category) return NULL;
1626 const guint rootnb = dt_util_string_count_char(category, '|');
1627 char *tags = NULL;
1628 sqlite3_stmt *stmt;
1629 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1630 "SELECT DISTINCT T.name FROM main.tagged_images AS I "
1631 "INNER JOIN data.tags AS T "
1632 "ON T.id = I.tagid AND SUBSTR(T.name, 1, LENGTH(?2)) = ?2 "
1633 "WHERE I.imgid = ?1",
1634 -1, &stmt, NULL);
1635 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
1636 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, category, -1, SQLITE_TRANSIENT);
1637 while(sqlite3_step(stmt) == SQLITE_ROW)
1638 {
1639 char *tag = (char *)sqlite3_column_text(stmt, 0);
1640 const guint tagnb = dt_util_string_count_char(tag, '|');
1641 if (tagnb >= rootnb + level)
1642 {
1643 gchar **pch = g_strsplit(tag, "|", -1);
1644 char *subtag = pch[rootnb + level];
1645 gboolean valid = TRUE;
1646 // check we have not yet this subtag in the list
1647 if(tags && strlen(tags) >= strlen(subtag) + 1)
1648 {
1649 gchar *found = g_strstr_len(tags, strlen(tags), subtag);
1650 if(found && found[strlen(subtag)] == ',')
1651 valid = FALSE;
1652 }
1653 if(valid)
1654 tags = dt_util_dstrcat(tags, "%s,", subtag);
1655 g_strfreev(pch);
1656 }
1657 }
1658 if(tags) tags[strlen(tags) - 1] = '\0'; // remove the last comma
1659 sqlite3_finalize(stmt);
1660 return tags;
1661 }
1662
dt_tag_get_tag_order_by_id(const uint32_t tagid,uint32_t * sort,gboolean * descending)1663 gboolean dt_tag_get_tag_order_by_id(const uint32_t tagid, uint32_t *sort,
1664 gboolean *descending)
1665 {
1666 gboolean res = FALSE;
1667 if(!sort || !descending) return res;
1668 sqlite3_stmt *stmt;
1669 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1670 "SELECT T.flags FROM data.tags AS T "
1671 "WHERE T.id = ?1",
1672 -1, &stmt, NULL);
1673 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1674 if(sqlite3_step(stmt) == SQLITE_ROW)
1675 {
1676 const uint32_t flags = sqlite3_column_int(stmt, 0);
1677 if((flags & (DT_TF_ORDER_SET)) == (DT_TF_ORDER_SET))
1678 {
1679 // the 16 upper bits of flags hold the order
1680 *sort = (flags & ~DT_TF_DESCENDING) >> 16;
1681 *descending = flags & DT_TF_DESCENDING;
1682 res = TRUE;
1683 }
1684 }
1685 sqlite3_finalize(stmt);
1686 return res;
1687 }
1688
dt_tag_get_tag_id_by_name(const char * const name)1689 uint32_t dt_tag_get_tag_id_by_name(const char * const name)
1690 {
1691 if(!name) return 0;
1692 uint32_t tagid = 0;
1693 const gboolean is_insensitive =
1694 dt_conf_is_equal("plugins/lighttable/tagging/case_sensitivity", "insensitive");
1695 const char *query = is_insensitive
1696 ? "SELECT T.id, T.flags FROM data.tags AS T "
1697 "WHERE T.name LIKE ?1"
1698 : "SELECT T.id, T.flags FROM data.tags AS T "
1699 "WHERE T.name = ?1";
1700 sqlite3_stmt *stmt;
1701 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1702 query, -1, &stmt, NULL);
1703 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 1, name, -1, SQLITE_TRANSIENT);
1704 if(sqlite3_step(stmt) == SQLITE_ROW)
1705 {
1706 tagid = sqlite3_column_int(stmt, 0);
1707 }
1708 sqlite3_finalize(stmt);
1709 return tagid;
1710 }
1711
dt_tag_set_tag_order_by_id(const uint32_t tagid,const uint32_t sort,const gboolean descending)1712 void dt_tag_set_tag_order_by_id(const uint32_t tagid, const uint32_t sort,
1713 const gboolean descending)
1714 {
1715 // use the upper 16 bits of flags to store the order
1716 const uint32_t flags = sort << 16 | (descending ? DT_TF_DESCENDING : 0)
1717 | DT_TF_ORDER_SET;
1718 sqlite3_stmt *stmt;
1719 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
1720 "UPDATE data.tags"
1721 " SET flags = (IFNULL(flags, 0) & ?3) | ?2 "
1722 "WHERE id = ?1",
1723 -1, &stmt, NULL);
1724 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, tagid);
1725 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, flags);
1726 DT_DEBUG_SQLITE3_BIND_INT(stmt, 3, DT_TF_ALL);
1727 sqlite3_step(stmt);
1728 sqlite3_finalize(stmt);
1729 }
1730
1731 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1732 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1733 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1734