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