1 /*
2  * Copyright (C) 2009, Nokia <ivan.frade@nokia.com>
3  * Copyright (C) 2014, Lanedo <martyn@lanedo.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library 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 GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA  02110-1301, USA.
19  */
20 
21 #include "config.h"
22 
23 #include <stdlib.h>
24 #include <time.h>
25 #include <locale.h>
26 
27 #include <glib.h>
28 #include <glib/gi18n.h>
29 #include <gio/gio.h>
30 
31 #include <libtracker-sparql/tracker-sparql.h>
32 
33 #include "tracker-tag.h"
34 
35 #define TAG_OPTIONS_ENABLED() \
36 	(resources || \
37 	 add_tag || \
38 	 remove_tag || \
39 	 list)
40 
41 static gint limit = 512;
42 static gint offset;
43 static gchar **resources;
44 static gboolean and_operator;
45 static gchar *add_tag;
46 static gchar *remove_tag;
47 static gchar *description;
48 static gboolean *list;
49 static gboolean show_resources;
50 
51 static GOptionEntry entries[] = {
52 	{ "list", 't', 0, G_OPTION_ARG_NONE, &list,
53 	  N_("List all tags (using FILTER if specified; FILTER always uses logical OR)"),
54 	  N_("FILTER"),
55 	},
56 	{ "show-files", 's', 0, G_OPTION_ARG_NONE, &show_resources,
57 	  N_("Show files associated with each tag (this is only used with --list)"),
58 	  NULL
59 	},
60 	{ "add", 'a', 0, G_OPTION_ARG_STRING, &add_tag,
61 	  N_("Add a tag (if FILEs are omitted, TAG is not associated with any files)"),
62 	  N_("TAG")
63 	},
64 	{ "delete", 'd', 0, G_OPTION_ARG_STRING, &remove_tag,
65 	  N_("Delete a tag (if FILEs are omitted, TAG is removed for all files)"),
66 	  N_("TAG")
67 	},
68 	{ "description", 'e', 0, G_OPTION_ARG_STRING, &description,
69 	  N_("Description for a tag (this is only used with --add)"),
70 	  N_("STRING")
71 	},
72 	{ "limit", 'l', 0, G_OPTION_ARG_INT, &limit,
73 	  N_("Limit the number of results shown"),
74 	  "512"
75 	},
76 	{ "offset", 'o', 0, G_OPTION_ARG_INT, &offset,
77 	  N_("Offset the results"),
78 	  "0"
79 	},
80 	{ "and-operator", 'n', 0, G_OPTION_ARG_NONE, &and_operator,
81 	  N_("Use AND for search terms instead of OR (the default)"),
82 	  NULL
83 	},
84 	{ G_OPTION_REMAINING, 0, 0,
85 	  G_OPTION_ARG_FILENAME_ARRAY, &resources,
86 	  N_("FILE…"),
87 	  N_("FILE [FILE…]")},
88 	{ NULL }
89 };
90 
91 static void
show_limit_warning(void)92 show_limit_warning (void)
93 {
94 	/* Display '...' so the user thinks there is
95 	 * more items.
96 	 */
97 	g_print ("  ...\n");
98 
99 	/* Display warning so the user knows this is
100 	 * not the WHOLE data set.
101 	 */
102 	g_printerr ("\n%s\n",
103 	            _("NOTE: Limit was reached, there are more items in the database not listed here"));
104 }
105 
106 static gchar *
get_escaped_sparql_string(const gchar * str)107 get_escaped_sparql_string (const gchar *str)
108 {
109 	GString *sparql;
110 
111 	sparql = g_string_new ("");
112 	g_string_append_c (sparql, '"');
113 
114 	while (*str != '\0') {
115 		gsize len = strcspn (str, "\t\n\r\"\\");
116 		g_string_append_len (sparql, str, len);
117 		str += len;
118 		switch (*str) {
119 		case '\t':
120 			g_string_append (sparql, "\\t");
121 			break;
122 		case '\n':
123 			g_string_append (sparql, "\\n");
124 			break;
125 		case '\r':
126 			g_string_append (sparql, "\\r");
127 			break;
128 		case '"':
129 			g_string_append (sparql, "\\\"");
130 			break;
131 		case '\\':
132 			g_string_append (sparql, "\\\\");
133 			break;
134 		default:
135 			continue;
136 		}
137 		str++;
138 	}
139 
140 	g_string_append_c (sparql, '"');
141 
142 	return g_string_free (sparql, FALSE);
143 }
144 
145 static gchar *
get_filter_string(GStrv resources,const gchar * subject,gboolean resources_are_urns,const gchar * tag)146 get_filter_string (GStrv        resources,
147                    const gchar *subject,
148                    gboolean     resources_are_urns,
149                    const gchar *tag)
150 {
151 	GString *filter;
152 	gint i, len;
153 
154 	if (!resources) {
155 		return NULL;
156 	}
157 
158 	len = g_strv_length (resources);
159 
160 	if (len < 1) {
161 		return NULL;
162 	}
163 
164 	filter = g_string_new ("");
165 
166 	g_string_append_printf (filter, "FILTER (");
167 
168 	if (tag) {
169 		g_string_append (filter, "(");
170 	}
171 
172 	for (i = 0; i < len; i++) {
173 		g_string_append_printf (filter, "%s = %s%s%s",
174 		                        subject,
175 		                        resources_are_urns ? "<" : "\"",
176 		                        resources[i],
177 		                        resources_are_urns ? ">" : "\"");
178 
179 		if (i < len - 1) {
180 			g_string_append (filter, " || ");
181 		}
182 	}
183 
184 	if (tag) {
185 		g_string_append_printf (filter, ") && ?t = <%s>", tag);
186 	}
187 
188 	g_string_append (filter, ")");
189 
190 	return g_string_free (filter, FALSE);
191 }
192 
193 static GStrv
get_uris(GStrv resources)194 get_uris (GStrv resources)
195 {
196 	GStrv uris;
197 	gint len, i;
198 
199 	if (!resources) {
200 		return NULL;
201 	}
202 
203 	len = g_strv_length (resources);
204 
205 	if (len < 1) {
206 		return NULL;
207 	}
208 
209 	uris = g_new0 (gchar *, len + 1);
210 
211 	for (i = 0; resources[i]; i++) {
212 		GFile *file;
213 
214 		file = g_file_new_for_commandline_arg (resources[i]);
215 		uris[i] = g_file_get_uri (file);
216 		g_object_unref (file);
217 	}
218 
219 	return uris;
220 }
221 
222 static TrackerSparqlCursor *
get_file_urns(TrackerSparqlConnection * connection,GStrv uris,const gchar * tag)223 get_file_urns (TrackerSparqlConnection *connection,
224 	       GStrv                    uris,
225 	       const gchar             *tag)
226 {
227 	TrackerSparqlCursor *cursor;
228 	gchar *query, *filter;
229 	GError *error = NULL;
230 
231 	filter = get_filter_string (uris, "?f", FALSE, tag);
232 	query = g_strdup_printf ("SELECT ?urn ?f "
233 	                         "WHERE { "
234 	                         "  ?urn "
235 	                         "    %s "
236 	                         "    nie:url ?f . "
237 	                         "  %s "
238 	                         "}",
239 	                         tag ? "nao:hasTag ?t ; " : "",
240 	                         filter ? filter : "");
241 
242 	cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
243 
244 	g_free (query);
245 	g_free (filter);
246 
247 	if (error) {
248 		g_print ("    %s, %s\n",
249 		         _("Could not get file URNs"),
250 		         error->message);
251 		g_error_free (error);
252 		return NULL;
253 	}
254 
255 	return cursor;
256 }
257 
258 static GStrv
result_to_strv(TrackerSparqlCursor * cursor,gint n_col)259 result_to_strv (TrackerSparqlCursor *cursor,
260                 gint                 n_col)
261 {
262 	GStrv strv;
263 	gint count, i;
264 
265 	if (!cursor) {
266 		return NULL;
267 	}
268 
269 	i = 0;
270 	count = 0;
271 
272 	/* Really no other option here, but we iterate the cursor
273 	 * first to get the length.
274 	 */
275 	while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
276 		count++;
277 	}
278 
279 	strv = g_new0 (gchar *, count + 1);
280 
281 	tracker_sparql_cursor_rewind (cursor);
282 
283 	while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
284 		const gchar *str;
285 
286 		str = tracker_sparql_cursor_get_string (cursor, n_col, NULL);
287 		strv[i++] = g_strdup (str);
288 	}
289 
290 	return strv;
291 }
292 
293 static void
get_all_tags_show_tag_id(TrackerSparqlConnection * connection,const gchar * id)294 get_all_tags_show_tag_id (TrackerSparqlConnection *connection,
295                           const gchar             *id)
296 {
297 	TrackerSparqlCursor *cursor;
298 	GError *error = NULL;
299 	gchar *query;
300 
301 	/* Get resources associated */
302 	query = g_strdup_printf ("SELECT ?uri WHERE {"
303 	                         "  ?urn a rdfs:Resource; "
304 	                         "  nie:url ?uri ; "
305 	                         "  nao:hasTag \"%s\" . "
306 	                         "}",
307 	                         id);
308 
309 	cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
310 	g_free (query);
311 
312 	if (error) {
313 		g_printerr ("    %s, %s\n",
314 		            _("Could not get files related to tag"),
315 		            error->message);
316 		g_error_free (error);
317 		return;
318 	}
319 
320 	if (!cursor) {
321 		/* To translators: This is to say there are no
322 		 * tags found with a particular unique ID. */
323 		g_print ("    %s\n", _("None"));
324 		return;
325 	}
326 
327 
328 	while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
329 		g_print ("    %s\n", tracker_sparql_cursor_get_string (cursor, 0, NULL));
330 	}
331 
332 	g_object_unref (cursor);
333 }
334 
335 static inline gchar *
get_filter_in_for_strv(GStrv resources,const gchar * subject)336 get_filter_in_for_strv (GStrv        resources,
337                         const gchar *subject)
338 {
339 	gchar *filter, *filter_in;
340 
341 	/* e.g. '?label IN ("foo", "bar")' */
342 	filter_in = g_strjoinv ("\",\"", resources);
343 	filter = g_strdup_printf ("FILTER (%s IN (\"%s\"))", subject, filter_in);
344 	g_free (filter_in);
345 
346 	return filter;
347 }
348 
349 static gboolean
get_all_resources_with_tags(TrackerSparqlConnection * connection,GStrv tags,gint search_offset,gint search_limit)350 get_all_resources_with_tags (TrackerSparqlConnection *connection,
351                              GStrv                    tags,
352                              gint                     search_offset,
353                              gint                     search_limit)
354 {
355 	TrackerSparqlCursor *cursor;
356 	GError *error = NULL;
357 	GStrv tag_urns, p;
358 	GString *s;
359 	gchar *filter, *query;
360 
361 	if (!tags) {
362 		return FALSE;
363 	}
364 
365 	/* First, get matching tags */
366 	filter = get_filter_in_for_strv (tags, "?label");
367 	query = g_strdup_printf ("SELECT ?t "
368 	                         "WHERE { "
369 	                         "  ?t a nao:Tag ;"
370 	                         "     nao:prefLabel ?label ."
371 	                         "  %s"
372 	                         "}",
373 	                         filter);
374 	g_free (filter);
375 
376 	cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
377 	g_free (query);
378 
379 	if (error) {
380 		g_printerr ("%s, %s\n",
381 		            _("Could not get all tags in the database"),
382 		            error->message);
383 		g_error_free (error);
384 
385 		return FALSE;
386 	}
387 
388 	tag_urns = result_to_strv (cursor, 0);
389 	if (!tag_urns) {
390 		g_print ("%s\n",
391 		         _("No files have been tagged"));
392 
393 		if (cursor) {
394 			g_object_unref (cursor);
395 		}
396 
397 		return TRUE;
398 	}
399 
400 	s = g_string_new ("");
401 
402 	for (p = tag_urns; p && *p; p++) {
403 		g_string_append_printf (s, "; nao:hasTag <%s>", *p);
404 	}
405 
406 	s = g_string_append (s, " .");
407 	filter = g_string_free (s, FALSE);
408 	g_strfreev (tag_urns);
409 
410 	query = g_strdup_printf ("SELECT DISTINCT nie:url(?r) "
411 	                         "WHERE {"
412 	                         "  ?r a rdfs:Resource %s"
413 	                         "} "
414 	                         "OFFSET %d "
415 	                         "LIMIT %d",
416 	                         filter,
417 	                         search_offset,
418 	                         search_limit);
419 	g_free (filter);
420 
421 	cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
422 	g_free (query);
423 
424 	if (error) {
425 		g_printerr ("%s, %s\n",
426 		            _("Could not get files for matching tags"),
427 		            error->message);
428 		g_error_free (error);
429 
430 		return FALSE;
431 	}
432 
433 	if (!cursor) {
434 		g_print ("%s\n",
435 		         _("No files were found matching ALL of those tags"));
436 	} else {
437 		gint count = 0;
438 
439 		g_print ("%s:\n", _("Files"));
440 
441 		while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
442 			g_print ("  %s\n",
443 			         tracker_sparql_cursor_get_string (cursor, 0, NULL));
444 			count++;
445 		}
446 
447 		if (count == 0) {
448 			/* To translators: This is to say there are no
449 			 * files found associated with multiple tags, e.g.:
450 			 *
451 			 *   Files:
452 			 *     None
453 			 *
454 			 */
455 			g_print ("  %s\n", _("None"));
456 		}
457 
458 		g_print ("\n");
459 
460 		if (count >= search_limit) {
461 			show_limit_warning ();
462 		}
463 
464 		g_object_unref (cursor);
465 	}
466 
467 	return TRUE;
468 }
469 
470 
471 static gboolean
get_all_tags(TrackerSparqlConnection * connection,GStrv resources,gint search_offset,gint search_limit,gboolean show_resources)472 get_all_tags (TrackerSparqlConnection *connection,
473               GStrv                    resources,
474               gint                     search_offset,
475               gint                     search_limit,
476               gboolean                 show_resources)
477 {
478 	TrackerSparqlCursor *cursor;
479 	GError *error = NULL;
480 	gchar *query;
481 	gchar *filter = NULL;
482 
483 	if (resources && g_strv_length (resources) > 0) {
484 		filter = get_filter_in_for_strv (resources, "?label");
485 	}
486 
487 	/* You might be asking, why not logical AND here, why
488 	 * logical OR for FILTER, well, tags can't have
489 	 * multiple labels is the simple answer.
490 	 */
491 	query = g_strdup_printf ("SELECT ?tag ?label nao:description(?tag) COUNT(?urns) "
492 	                         "WHERE {"
493 	                         "  ?tag a nao:Tag ;"
494 	                         "  nao:prefLabel ?label ."
495 	                         "  OPTIONAL {"
496 	                         "     ?urns nao:hasTag ?tag"
497 	                         "  } ."
498 	                         "  %s"
499 	                         "} "
500 	                         "GROUP BY ?tag "
501 	                         "ORDER BY ASC(?label) "
502 	                         "OFFSET %d "
503 	                         "LIMIT %d",
504 	                         filter ? filter : "",
505 	                         search_offset,
506 	                         search_limit);
507 	g_free (filter);
508 
509 	cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
510 	g_free (query);
511 
512 	if (error) {
513 		g_printerr ("%s, %s\n",
514 		            _("Could not get all tags"),
515 		            error->message);
516 		g_error_free (error);
517 
518 		return FALSE;
519 	}
520 
521 	if (!cursor) {
522 		g_print ("%s\n",
523 		         _("No tags were found"));
524 	} else {
525 		gint count = 0;
526 
527 		g_print ("%s:\n", _("Tags (shown by name)"));
528 
529 		while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
530 			const gchar *id;
531 			const gchar *tag;
532 			const gchar *description;
533 			const gchar *resources;
534 			gint n_resources = 0;
535 
536 			id = tracker_sparql_cursor_get_string (cursor, 0, NULL);
537 			resources = tracker_sparql_cursor_get_string (cursor, 3, NULL);
538 			n_resources = atoi (resources);
539 
540 			tag = tracker_sparql_cursor_get_string (cursor, 1, NULL);
541 			description = tracker_sparql_cursor_get_string (cursor, 2, NULL);
542 
543 			if (description && *description == '\0') {
544 				description = NULL;
545 			}
546 
547 			g_print ("  %s %s%s%s\n",
548 			         tag,
549 			         description ? "(" : "",
550 			         description ? description : "",
551 			         description ? ")" : "");
552 
553 			if (show_resources && n_resources > 0) {
554 				get_all_tags_show_tag_id (connection, id);
555 			} else {
556 				g_print ("    %s\n", id);
557 				g_print ("    ");
558 				g_print (g_dngettext (NULL,
559 				                      "%d file",
560 				                      "%d files",
561 				                      n_resources),
562 				         n_resources);
563 				g_print ("\n");
564 			}
565 
566 			count++;
567 		}
568 
569 		if (count == 0) {
570 			/* To translators: This is to say there are no
571 			 * resources found associated with this tag, e.g.:
572 			 *
573 			 *   Tags (shown by name):
574 			 *     None
575 			 *
576 			 */
577 			g_print ("  %s\n", _("None"));
578 		}
579 
580 		g_print ("\n");
581 
582 		if (count >= search_limit) {
583 			show_limit_warning ();
584 		}
585 
586 		g_object_unref (cursor);
587 	}
588 
589 	return TRUE;
590 }
591 
592 static void
print_file_report(TrackerSparqlCursor * cursor,GStrv uris,const gchar * found_msg,const gchar * not_found_msg)593 print_file_report (TrackerSparqlCursor *cursor,
594                    GStrv                uris,
595                    const gchar         *found_msg,
596                    const gchar         *not_found_msg)
597 {
598 	gint i;
599 
600 	if (!cursor || !uris) {
601 		g_print ("  %s\n", _("No files were modified"));
602 		return;
603 	}
604 
605 	for (i = 0; uris[i]; i++) {
606 		gboolean found = FALSE;
607 
608 		tracker_sparql_cursor_rewind (cursor);
609 
610 		while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
611 			const gchar *str;
612 
613 			str = tracker_sparql_cursor_get_string (cursor, 1, NULL);
614 
615 			if (g_strcmp0 (str, uris[i]) == 0) {
616 				found = TRUE;
617 				break;
618 			}
619 		}
620 
621 		g_print ("  %s: %s\n",
622 		         found ? found_msg : not_found_msg,
623 		         uris[i]);
624 	}
625 }
626 
627 static gboolean
add_tag_for_urns(TrackerSparqlConnection * connection,GStrv resources,const gchar * tag,const gchar * description)628 add_tag_for_urns (TrackerSparqlConnection *connection,
629                   GStrv                    resources,
630                   const gchar             *tag,
631                   const gchar             *description)
632 {
633 	TrackerSparqlCursor *cursor = NULL;
634 	GError *error = NULL;
635 	GStrv  uris = NULL, urns_strv = NULL;
636 	gchar *tag_escaped;
637 	gchar *query;
638 
639 	tag_escaped = get_escaped_sparql_string (tag);
640 
641 	if (resources) {
642 		uris = get_uris (resources);
643 
644 		if (!uris) {
645 			return FALSE;
646 		}
647 
648 		cursor = get_file_urns (connection, uris, NULL);
649 
650 		if (!cursor) {
651 			g_printerr ("%s\n", _("Files do not exist or aren’t indexed"));
652 			g_strfreev (uris);
653 			return FALSE;
654 		}
655 
656 		urns_strv = result_to_strv (cursor, 0);
657 
658 		if (!urns_strv || g_strv_length (urns_strv) < 1) {
659 			g_printerr ("%s\n", _("Files do not exist or aren’t indexed"));
660 			g_object_unref (cursor);
661 			g_strfreev (uris);
662 			return FALSE;
663 		}
664 	}
665 
666 	if (description) {
667 		gchar *description_escaped;
668 
669 		description_escaped = get_escaped_sparql_string (description);
670 
671 		query = g_strdup_printf ("INSERT { "
672 					 "  _:tag a nao:Tag;"
673 					 "  nao:prefLabel %s ;"
674 					 "  nao:description %s ."
675 					 "} "
676 					 "WHERE {"
677 					 "  OPTIONAL {"
678 					 "     ?tag a nao:Tag ;"
679 					 "     nao:prefLabel %s ."
680 					 "  } ."
681 					 "  FILTER (!bound(?tag)) "
682 					 "}",
683 					 tag_escaped,
684 					 description_escaped,
685 					 tag_escaped);
686 
687 		g_free (description_escaped);
688 	} else {
689 		query = g_strdup_printf ("INSERT { "
690 					 "  _:tag a nao:Tag;"
691 					 "  nao:prefLabel %s ."
692 					 "} "
693 					 "WHERE {"
694 					 "  OPTIONAL {"
695 					 "     ?tag a nao:Tag ;"
696 					 "     nao:prefLabel %s ."
697 					 "  } ."
698 					 "  FILTER (!bound(?tag)) "
699 					 "}",
700 					 tag_escaped,
701 					 tag_escaped);
702 	}
703 
704 	tracker_sparql_connection_update (connection, query, 0, NULL, &error);
705 	g_free (query);
706 
707 	if (error) {
708 		g_printerr ("%s, %s\n",
709 		            _("Could not add tag"),
710 		            error->message);
711 
712 		if (cursor) {
713 			g_object_unref (cursor);
714 		}
715 
716 		g_error_free (error);
717 		g_free (tag_escaped);
718 		g_strfreev (urns_strv);
719 		g_strfreev (uris);
720 
721 		return FALSE;
722 	}
723 
724 	g_print ("%s\n",
725 	         _("Tag was added successfully"));
726 
727 	/* First we check if the tag is already set and only add if it
728 	 * is, then we add the urns specified to the new tag.
729 	 */
730 	if (urns_strv) {
731 		gchar *filter;
732 
733 		filter = get_filter_string (urns_strv, "?urn", TRUE, NULL);
734 
735 		/* Add tag to specific urns */
736 		query = g_strdup_printf ("INSERT { "
737 		                         "  ?urn nao:hasTag ?id "
738 		                         "} "
739 		                         "WHERE {"
740 		                         "  ?urn nie:url ?f ."
741 		                         "  ?id nao:prefLabel %s "
742 		                         "  %s "
743 		                         "}",
744 		                         tag_escaped,
745 		                         filter ? filter : "");
746 
747 		tracker_sparql_connection_update (connection, query, 0, NULL, &error);
748 		g_strfreev (urns_strv);
749 		g_free (filter);
750 		g_free (query);
751 
752 		if (error) {
753 			g_printerr ("%s, %s\n",
754 			            _("Could not add tag to files"),
755 			            error->message);
756 			g_object_unref (cursor);
757 			g_error_free (error);
758 			g_free (tag_escaped);
759 			g_strfreev (uris);
760 
761 			return FALSE;
762 		}
763 
764 		print_file_report (cursor, uris, _("Tagged"),
765 		                   _("Not tagged, file is not indexed"));
766 	}
767 
768 	g_strfreev (uris);
769 	g_free (tag_escaped);
770 
771 	if (cursor) {
772 		g_object_unref (cursor);
773 	}
774 
775 	return TRUE;
776 }
777 
778 static gboolean
remove_tag_for_urns(TrackerSparqlConnection * connection,GStrv resources,const gchar * tag)779 remove_tag_for_urns (TrackerSparqlConnection *connection,
780                      GStrv                    resources,
781                      const gchar             *tag)
782 {
783 	TrackerSparqlCursor *urns_cursor = NULL;
784 	GError *error = NULL;
785 	gchar *tag_escaped;
786 	gchar *query;
787 	GStrv uris;
788 
789 	tag_escaped = get_escaped_sparql_string (tag);
790 	uris = get_uris (resources);
791 
792 	if (uris && *uris) {
793 		TrackerSparqlCursor *tag_cursor;
794 		gchar *filter;
795 		const gchar *urn;
796 		GStrv urns_strv;
797 
798 		/* Get all tags urns */
799 		query = g_strdup_printf ("SELECT ?tag "
800 		                         "WHERE {"
801 		                         "  ?tag a nao:Tag ."
802 		                         "  ?tag nao:prefLabel %s "
803 		                         "}",
804 		                         tag_escaped);
805 
806 		tag_cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
807 		g_free (query);
808 
809 		if (error) {
810 			g_printerr ("%s, %s\n",
811 			            _("Could not get tag by label"),
812 			            error->message);
813 			g_error_free (error);
814 			g_free (tag_escaped);
815 			g_strfreev (uris);
816 
817 			return FALSE;
818 		}
819 
820 		if (!tag_cursor || !tracker_sparql_cursor_next (tag_cursor, NULL, NULL)) {
821 			g_print ("%s\n",
822 			         _("No tags were found by that name"));
823 
824 			g_free (tag_escaped);
825 			g_strfreev (uris);
826 
827 			if (tag_cursor) {
828 				g_object_unref (tag_cursor);
829 			}
830 
831 			return TRUE;
832 		}
833 
834 		urn = tracker_sparql_cursor_get_string (tag_cursor, 0, NULL);
835 		urns_cursor = get_file_urns (connection, uris, urn);
836 
837 		if (!urns_cursor || !tracker_sparql_cursor_next (urns_cursor, NULL, NULL)) {
838 			g_print ("%s\n",
839 			         _("None of the files had this tag set"));
840 
841 			g_strfreev (uris);
842 			g_free (tag_escaped);
843 			g_object_unref (tag_cursor);
844 
845 			if (urns_cursor) {
846 				g_object_unref (urns_cursor);
847 			}
848 
849 			return TRUE;
850 		}
851 
852 		urns_strv = result_to_strv (urns_cursor, 0);
853 		filter = get_filter_string (urns_strv, "?urn", TRUE, urn);
854 		g_strfreev (urns_strv);
855 
856 		query = g_strdup_printf ("DELETE { "
857 		                         "  ?urn nao:hasTag ?t "
858 		                         "} "
859 		                         "WHERE { "
860 		                         "  ?urn nao:hasTag ?t . "
861 		                         "  %s "
862 		                         "}",
863 		                         filter ? filter : "");
864 		g_free (filter);
865 
866 		g_object_unref (tag_cursor);
867 	} else {
868 		/* Remove tag completely */
869 		query = g_strdup_printf ("DELETE { "
870 		                         "  ?tag a nao:Tag "
871 		                         "} "
872 		                         "WHERE {"
873 		                         "  ?tag nao:prefLabel %s "
874 		                         "}",
875 		                         tag_escaped);
876 	}
877 
878 	g_free (tag_escaped);
879 
880 	tracker_sparql_connection_update (connection, query, 0, NULL, &error);
881 	g_free (query);
882 
883 	if (error) {
884 		g_printerr ("%s, %s\n",
885 		            _("Could not remove tag"),
886 		            error->message);
887 		g_error_free (error);
888 
889 		return FALSE;
890 	}
891 
892 	g_print ("%s\n", _("Tag was removed successfully"));
893 
894 	if (urns_cursor) {
895 		print_file_report (urns_cursor, uris,
896 		                   _("Untagged"),
897 		                   _("File not indexed or already untagged"));
898 		g_object_unref (urns_cursor);
899 	}
900 
901 	g_strfreev (uris);
902 
903 	return TRUE;
904 }
905 
906 static gboolean
get_tags_by_file(TrackerSparqlConnection * connection,const gchar * uri)907 get_tags_by_file (TrackerSparqlConnection *connection,
908                   const gchar             *uri)
909 {
910 	TrackerSparqlCursor *cursor;
911 	GError *error = NULL;
912 	gchar *query;
913 
914 	query = g_strdup_printf ("SELECT ?tags ?labels "
915 	                         "WHERE {"
916 	                         "  ?urn nao:hasTag ?tags ;"
917 	                         "  nie:url <%s> ."
918 	                         "  ?tags a nao:Tag ;"
919 	                         "  nao:prefLabel ?labels "
920 	                         "} "
921 	                         "ORDER BY ASC(?labels)",
922 	                         uri);
923 
924 	cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
925 	g_free (query);
926 
927 	if (error) {
928 		g_printerr ("%s, %s\n",
929 		            _("Could not get all tags"),
930 		            error->message);
931 		g_error_free (error);
932 
933 		return FALSE;
934 	}
935 
936 	if (!cursor) {
937 		g_print ("  %s\n",
938 		         _("No tags were found"));
939 	} else {
940 		gint count = 0;
941 
942 		while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
943 			g_print ("  %s\n", tracker_sparql_cursor_get_string (cursor, 1, NULL));
944 			count++;
945 		}
946 
947 		if (count == 0) {
948 			/* To translators: This is to say there are no
949 			 * tags found for a particular file, e.g.:
950 			 *
951 			 *   /path/to/some/file:
952 			 *     None
953 			 *
954 			 */
955 			g_print ("  %s\n", _("None"));
956 		}
957 
958 		g_print ("\n");
959 
960 		g_object_unref (cursor);
961 	}
962 
963 	return TRUE;
964 }
965 
966 static int
tag_run(void)967 tag_run (void)
968 {
969 	TrackerSparqlConnection *connection;
970 	GError *error = NULL;
971 
972 	connection = tracker_sparql_connection_get (NULL, &error);
973 
974 	if (!connection) {
975 		g_printerr ("%s: %s\n",
976 		            _("Could not establish a connection to Tracker"),
977 		            error ? error->message : _("No error given"));
978 		g_clear_error (&error);
979 		return EXIT_FAILURE;
980 	}
981 
982 	if (list) {
983 		gboolean success;
984 
985 		if (G_UNLIKELY (and_operator)) {
986 			success = get_all_resources_with_tags (connection, resources, offset, limit);
987 		} else {
988 			success = get_all_tags (connection, resources, offset, limit, show_resources);
989 		}
990 		g_object_unref (connection);
991 
992 		return success ? EXIT_SUCCESS : EXIT_FAILURE;
993 	}
994 
995 	if (add_tag) {
996 		gboolean success;
997 
998 		success = add_tag_for_urns (connection, resources, add_tag, description);
999 		g_object_unref (connection);
1000 
1001 		return success ? EXIT_SUCCESS : EXIT_FAILURE;
1002 	}
1003 
1004 	if (remove_tag) {
1005 		gboolean success;
1006 
1007 		success = remove_tag_for_urns (connection, resources, remove_tag);
1008 		g_object_unref (connection);
1009 
1010 		return success ? EXIT_SUCCESS : EXIT_FAILURE;
1011 	}
1012 
1013 	if (resources) {
1014 		gboolean success = TRUE;
1015 		gchar **p;
1016 
1017 		for (p = resources; *p; p++) {
1018 			GFile *file;
1019 			gchar *uri;
1020 
1021 			file = g_file_new_for_commandline_arg (*p);
1022 			uri = g_file_get_uri (file);
1023 			g_object_unref (file);
1024 
1025 			g_print ("%s\n", uri);
1026 			success &= get_tags_by_file (connection, uri);
1027 
1028 			g_free (uri);
1029 		}
1030 
1031 		g_object_unref (connection);
1032 
1033 		return success ? EXIT_SUCCESS : EXIT_FAILURE;
1034 	}
1035 
1036 	g_object_unref (connection);
1037 
1038 	/* This is a failure because we should have done something.
1039 	 * This code should never be reached in practise.
1040 	 */
1041 	return EXIT_FAILURE;
1042 }
1043 
1044 static int
tag_run_default(void)1045 tag_run_default (void)
1046 {
1047 	GOptionContext *context;
1048 	gchar *help;
1049 
1050 	context = g_option_context_new (NULL);
1051 	g_option_context_add_main_entries (context, entries, NULL);
1052 	help = g_option_context_get_help (context, TRUE, NULL);
1053 	g_option_context_free (context);
1054 	g_printerr ("%s\n", help);
1055 	g_free (help);
1056 
1057 	return EXIT_FAILURE;
1058 }
1059 
1060 static gboolean
tag_options_enabled(void)1061 tag_options_enabled (void)
1062 {
1063 	return TAG_OPTIONS_ENABLED ();
1064 }
1065 
1066 int
tracker_tag(int argc,const char ** argv)1067 tracker_tag (int argc, const char **argv)
1068 {
1069 	GOptionContext *context;
1070 	GError *error = NULL;
1071 	const gchar *failed;
1072 
1073 	context = g_option_context_new (NULL);
1074 	g_option_context_add_main_entries (context, entries, NULL);
1075 
1076 	argv[0] = "tracker tag";
1077 
1078 	if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) {
1079 		g_printerr ("%s, %s\n", _("Unrecognized options"), error->message);
1080 		g_error_free (error);
1081 		g_option_context_free (context);
1082 		return EXIT_FAILURE;
1083 	}
1084 
1085 	g_option_context_free (context);
1086 
1087 	if (!list && show_resources) {
1088 		failed = _("The --list option is required for --show-files");
1089 	} else if (and_operator && (!list || !resources)) {
1090 		failed = _("The --and-operator option can only be used with --list and tag label arguments");
1091 	} else if (add_tag && remove_tag) {
1092 		failed = _("Add and delete actions can not be used together");
1093 	} else if (description && !add_tag) {
1094 		failed = _("The --description option can only be used with --add");
1095 	} else {
1096 		failed = NULL;
1097 	}
1098 
1099 	if (failed) {
1100 		g_printerr ("%s\n\n", failed);
1101 		return EXIT_FAILURE;
1102 	}
1103 
1104 	if (tag_options_enabled ()) {
1105 		return tag_run ();
1106 	}
1107 
1108 	return tag_run_default ();
1109 }
1110