No issues found
1 /*
2 * Copyright (C) 2009, Nokia <ivan.frade@nokia.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include "config.h"
21
22 #include <sys/param.h>
23 #include <stdlib.h>
24 #include <time.h>
25 #include <locale.h>
26
27 #include <glib.h>
28 #include <glib/gi18n.h>
29
30 #include <libtracker-sparql/tracker-sparql.h>
31
32 #define ABOUT \
33 "Tracker " PACKAGE_VERSION "\n"
34
35 #define LICENSE \
36 "This program is free software and comes without any warranty.\n" \
37 "It is licensed under version 2 or later of the General Public " \
38 "License which can be viewed at:\n" \
39 "\n" \
40 " http://www.gnu.org/licenses/gpl.txt\n"
41
42 static gboolean parse_list_notifies (const gchar *option_name,
43 const gchar *value,
44 gpointer data,
45 GError **error);
46 static gboolean parse_list_indexes (const gchar *option_name,
47 const gchar *value,
48 gpointer data,
49 GError **error);
50
51 static gchar *file;
52 static gchar *query;
53 static gboolean update;
54 static gboolean list_classes;
55 static gboolean list_class_prefixes;
56 static gchar *list_properties;
57 static gchar *list_notifies;
58 static gchar *list_indexes;
59 static gboolean print_version;
60 static gchar *search;
61
62 static GOptionEntry entries[] = {
63 { "file", 'f', 0, G_OPTION_ARG_FILENAME, &file,
64 N_("Path to use to run a query or update from file"),
65 N_("FILE"),
66 },
67 { "query", 'q', 0, G_OPTION_ARG_STRING, &query,
68 N_("SPARQL query"),
69 N_("SPARQL"),
70 },
71 { "update", 'u', 0, G_OPTION_ARG_NONE, &update,
72 N_("This is used with --query and for database updates only."),
73 NULL,
74 },
75 { "list-classes", 'c', 0, G_OPTION_ARG_NONE, &list_classes,
76 N_("Retrieve classes"),
77 NULL,
78 },
79 { "list-class-prefixes", 'x', 0, G_OPTION_ARG_NONE, &list_class_prefixes,
80 N_("Retrieve class prefixes"),
81 NULL,
82 },
83 { "list-properties", 'p', 0, G_OPTION_ARG_STRING, &list_properties,
84 N_("Retrieve properties for a class, prefixes can be used too (e.g. rdfs:Resource)"),
85 N_("CLASS"),
86 },
87 { "list-notifies", 'n', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, parse_list_notifies,
88 N_("Retrieve classes which notify changes in the database (CLASS is optional)"),
89 N_("CLASS"),
90 },
91 { "list-indexes", 'i', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, parse_list_indexes,
92 N_("Retrieve indexes used in database to improve performance (PROPERTY is optional) "),
93 N_("PROPERTY"),
94 },
95 { "search", 's', 0, G_OPTION_ARG_STRING, &search,
96 N_("Search for a class or property and display more information (e.g. Document)"),
97 N_("CLASS/PROPERTY"),
98 },
99 { "version", 'V', 0, G_OPTION_ARG_NONE, &print_version,
100 N_("Print version"),
101 NULL,
102 },
103 { NULL }
104 };
105
106 static GHashTable *
107 get_prefixes (TrackerSparqlConnection *connection)
108 {
109 TrackerSparqlCursor *cursor;
110 GError *error = NULL;
111 GHashTable *retval;
112 const gchar *query;
113
114 retval = g_hash_table_new_full (g_str_hash,
115 g_str_equal,
116 g_free,
117 g_free);
118
119 /* FIXME: Would like to get this in the same SPARQL that we
120 * use to get the info, but doesn't seem possible at the
121 * moment with the limited string manipulation features we
122 * support in SPARQL.
123 */
124 query = "SELECT ?ns ?prefix "
125 "WHERE {"
126 " ?ns a tracker:Namespace ;"
127 " tracker:prefix ?prefix "
128 "}";
129
130 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
131
132 if (error) {
133 g_printerr ("%s, %s\n",
134 _("Unable to retrieve namespace prefixes"),
135 error->message);
136
137 g_error_free (error);
138 return retval;
139 }
140
141 if (!cursor) {
142 g_printerr ("%s\n", _("No namespace prefixes were returned"));
143 return retval;
144 }
145
146 while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
147 const gchar *key, *value;
148
149 key = tracker_sparql_cursor_get_string (cursor, 0, NULL);
150 value = tracker_sparql_cursor_get_string (cursor, 1, NULL);
151
152 if (!key || !value) {
153 continue;
154 }
155
156 g_hash_table_insert (retval,
157 g_strndup (key, strlen (key) - 1),
158 g_strdup (value));
159 }
160
161 if (cursor) {
162 g_object_unref (cursor);
163 }
164
165 return retval;
166 }
167
168 static gchar *
169 get_class_from_prefix (TrackerSparqlConnection *connection,
170 const gchar *prefix)
171 {
172 GError *error = NULL;
173 TrackerSparqlCursor *cursor;
174 const gchar *query;
175 gchar *found = NULL;
176
177 query = "SELECT ?prefix ?ns "
178 "WHERE {"
179 " ?ns a tracker:Namespace ;"
180 " tracker:prefix ?prefix "
181 "}";
182
183 /* We have namespace prefix, get full name */
184 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
185
186 if (error) {
187 g_printerr ("%s, %s\n",
188 _("Could not get namespace prefixes"),
189 error->message);
190 g_error_free (error);
191
192 return NULL;
193 }
194
195 if (!cursor) {
196 g_printerr ("%s\n",
197 _("No namespace prefixes were found"));
198
199 return NULL;
200 }
201
202 while (tracker_sparql_cursor_next (cursor, NULL, NULL) && !found) {
203 const gchar *class_prefix, *class_name;
204
205 class_prefix = tracker_sparql_cursor_get_string (cursor, 0, NULL);
206 class_name = tracker_sparql_cursor_get_string (cursor, 1, NULL);
207
208 if (strcmp (class_prefix, prefix) == 0) {
209 found = g_strdup (class_name);
210 }
211 }
212
213 g_object_unref (cursor);
214
215 return found;
216 }
217
218 static gboolean
219 parse_list_notifies (const gchar *option_name,
220 const gchar *value,
221 gpointer data,
222 GError **error)
223 {
224 if (!value) {
225 list_notifies = g_strdup ("");
226 } else {
227 list_notifies = g_strdup (value);
228 }
229
230 return TRUE;
231 }
232
233 static gboolean
234 parse_list_indexes (const gchar *option_name,
235 const gchar *value,
236 gpointer data,
237 GError **error)
238 {
239 if (!value) {
240 list_indexes = g_strdup ("");
241 } else {
242 list_indexes = g_strdup (value);
243 }
244
245 return TRUE;
246 }
247
248 inline static gchar *
249 get_shorthand (GHashTable *prefixes,
250 const gchar *namespace)
251 {
252 gchar *hash;
253
254 hash = strrchr (namespace, '#');
255
256 if (hash) {
257 gchar *property;
258 const gchar *prefix;
259
260 property = hash + 1;
261 *hash = '\0';
262
263 prefix = g_hash_table_lookup (prefixes, namespace);
264
265 return g_strdup_printf ("%s:%s", prefix, property);
266 }
267
268 return g_strdup (namespace);
269 }
270
271 inline static gchar *
272 get_shorthand_for_offsets (GHashTable *prefixes,
273 const gchar *str)
274 {
275 GString *result = NULL;
276 gchar **properties;
277 gint i;
278
279 if (!str) {
280 return NULL;
281 }
282
283 properties = g_strsplit (str, ",", -1);
284 if (!properties) {
285 return NULL;
286 }
287
288 for (i = 0; properties[i] != NULL && properties[i + 1] != NULL; i += 2) {
289 const gchar *property;
290 const gchar *offset;
291 gchar *shorthand;
292
293 property = properties[i];
294 offset = properties[i + 1];
295
296 if (!property || !offset) {
297 g_warning ("Expected property AND offset to be valid for fts:offset results");
298 continue;
299 }
300
301 shorthand = get_shorthand (prefixes, property);
302 /* shorthand = g_hash_table_lookup (prefixes, property); */
303
304 if (!shorthand) {
305 shorthand = g_strdup (property);
306 }
307
308 if (!result) {
309 result = g_string_new ("");
310 } else {
311 result = g_string_append_c (result, ' ');
312 }
313
314 g_string_append_printf (result, "%s:%s", shorthand, offset);
315 g_free (shorthand);
316 }
317
318 g_strfreev (properties);
319
320 return result ? g_string_free (result, FALSE) : NULL;
321 }
322
323 static void
324 print_cursor_with_ftsoffsets (TrackerSparqlCursor *cursor,
325 GHashTable *prefixes,
326 const gchar *none_found,
327 const gchar *heading,
328 gboolean only_first_col)
329 {
330 if (!cursor) {
331 g_print ("%s\n", none_found);
332 } else {
333 gint count = 0;
334
335 g_print ("%s:\n", heading);
336
337 if (only_first_col) {
338 while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
339 const gchar *str;
340 gchar *shorthand;
341
342 str = tracker_sparql_cursor_get_string (cursor, 0, NULL);
343 shorthand = get_shorthand_for_offsets (prefixes, str);
344 g_print (" %s\n", shorthand ? shorthand : str);
345 g_free (shorthand);
346 count++;
347 }
348 } else {
349 while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
350 gint col;
351
352 for (col = 0; col < tracker_sparql_cursor_get_n_columns (cursor); col++) {
353 const gchar *str;
354 gchar *shorthand;
355
356 str = tracker_sparql_cursor_get_string (cursor, col, NULL);
357 shorthand = get_shorthand_for_offsets (prefixes, str);
358 g_print ("%c %s",
359 col == 0 ? ' ' : ',',
360 shorthand ? shorthand : str);
361 g_free (shorthand);
362 }
363
364 g_print ("\n");
365
366 count++;
367 }
368 }
369
370 if (count == 0) {
371 g_print (" %s\n", _("None"));
372 }
373
374 g_print ("\n");
375
376 g_object_unref (cursor);
377 }
378 }
379
380 static void
381 print_cursor (TrackerSparqlCursor *cursor,
382 const gchar *none_found,
383 const gchar *heading,
384 gboolean only_first_col)
385 {
386 if (!cursor) {
387 g_print ("%s\n", none_found);
388 } else {
389 gint count = 0;
390
391 g_print ("%s:\n", heading);
392
393 if (only_first_col) {
394 while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
395 g_print (" %s\n", tracker_sparql_cursor_get_string (cursor, 0, NULL));
396 count++;
397 }
398 } else {
399 while (tracker_sparql_cursor_next (cursor, NULL, NULL)) {
400 gint col;
401
402 for (col = 0; col < tracker_sparql_cursor_get_n_columns (cursor); col++) {
403 g_print ("%c %s",
404 col == 0 ? ' ' : ',',
405 tracker_sparql_cursor_get_string (cursor, col, NULL));
406 }
407
408 g_print ("\n");
409
410 count++;
411 }
412 }
413
414 if (count == 0) {
415 g_print (" %s\n", _("None"));
416 }
417
418 g_print ("\n");
419
420 g_object_unref (cursor);
421 }
422 }
423
424 int
425 main (int argc, char **argv)
426 {
427 TrackerSparqlConnection *connection;
428 TrackerSparqlCursor *cursor;
429 GOptionContext *context;
430 GError *error = NULL;
431 const gchar *error_message;
432
433 setlocale (LC_ALL, "");
434
435 bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
436 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
437 textdomain (GETTEXT_PACKAGE);
438
439 context = g_option_context_new (_("- Query or update using SPARQL"));
440
441 g_option_context_add_main_entries (context, entries, NULL);
442 g_option_context_parse (context, &argc, &argv, NULL);
443
444 if (print_version) {
445 g_print ("\n" ABOUT "\n" LICENSE "\n");
446 g_option_context_free (context);
447
448 return EXIT_SUCCESS;
449 }
450
451 if (!list_classes && !list_class_prefixes && !list_properties &&
452 !list_notifies && !list_indexes && !search && !file && !query) {
453 error_message = _("An argument must be supplied");
454 } else if (file && query) {
455 error_message = _("File and query can not be used together");
456 } else {
457 error_message = NULL;
458 }
459
460 if (error_message) {
461 gchar *help;
462
463 g_printerr ("%s\n\n", error_message);
464
465 help = g_option_context_get_help (context, TRUE, NULL);
466 g_option_context_free (context);
467 g_printerr ("%s", help);
468 g_free (help);
469
470 return EXIT_FAILURE;
471 }
472
473 g_option_context_free (context);
474
475 connection = tracker_sparql_connection_get (NULL, &error);
476
477 if (!connection) {
478 g_printerr ("%s: %s\n",
479 _("Could not establish a connection to Tracker"),
480 error ? error->message : _("No error given"));
481 g_clear_error (&error);
482 return EXIT_FAILURE;
483 }
484
485 if (list_classes) {
486 const gchar *query;
487
488 query = "SELECT ?c WHERE { ?c a rdfs:Class }";
489 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
490
491 if (error) {
492 g_printerr ("%s, %s\n",
493 _("Could not list classes"),
494 error->message);
495 g_error_free (error);
496 g_object_unref (connection);
497
498 return EXIT_FAILURE;
499 }
500
501 print_cursor (cursor, _("No classes were found"), _("Classes"), TRUE);
502 }
503
504 if (list_class_prefixes) {
505 const gchar *query;
506
507 query = "SELECT ?prefix ?ns "
508 "WHERE {"
509 " ?ns a tracker:Namespace ;"
510 " tracker:prefix ?prefix "
511 "}";
512
513 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
514
515 if (error) {
516 g_printerr ("%s, %s\n",
517 _("Could not list class prefixes"),
518 error->message);
519 g_error_free (error);
520 g_object_unref (connection);
521
522 return EXIT_FAILURE;
523 }
524
525 print_cursor (cursor, _("No class prefixes were found"), _("Prefixes"), FALSE);
526 }
527
528 if (list_properties) {
529 gchar *query;
530 gchar *class_name;
531
532 if (g_str_has_prefix (list_properties, "http://")) {
533 /* We have full class name */
534 class_name = g_strdup (list_properties);
535 } else {
536 gchar *p;
537 gchar *prefix, *property;
538 gchar *class_name_no_property;
539
540 prefix = g_strdup (list_properties);
541 p = strchr (prefix, ':');
542
543 if (!p) {
544 g_printerr ("%s\n",
545 _("Could not find property for class prefix, "
546 "e.g. :Resource in 'rdfs:Resource'"));
547 g_free (prefix);
548 g_object_unref (connection);
549
550 return EXIT_FAILURE;
551 }
552
553 property = g_strdup (p + 1);
554 *p = '\0';
555
556 class_name_no_property = get_class_from_prefix (connection, prefix);
557 g_free (prefix);
558
559 if (!class_name_no_property) {
560 g_free (property);
561 g_object_unref (connection);
562
563 return EXIT_FAILURE;
564 }
565
566 class_name = g_strconcat (class_name_no_property, property, NULL);
567 g_free (class_name_no_property);
568 g_free (property);
569 }
570
571 query = g_strdup_printf ("SELECT ?p "
572 "WHERE {"
573 " ?p a rdf:Property ;"
574 " rdfs:domain <%s>"
575 "}",
576 class_name);
577
578 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
579 g_free (query);
580 g_free (class_name);
581
582 if (error) {
583 g_printerr ("%s, %s\n",
584 _("Could not list properties"),
585 error->message);
586 g_error_free (error);
587 g_object_unref (connection);
588
589 return EXIT_FAILURE;
590 }
591
592 print_cursor (cursor, _("No properties were found"), _("Properties"), TRUE);
593 }
594
595 if (list_notifies) {
596 gchar *query;
597
598 /* First list classes */
599 if (*list_notifies == '\0') {
600 query = g_strdup_printf ("SELECT ?c "
601 "WHERE {"
602 " ?c a rdfs:Class ."
603 " ?c tracker:notify true ."
604 "}");
605 } else {
606 query = g_strdup_printf ("SELECT ?c "
607 "WHERE {"
608 " ?c a rdfs:Class ."
609 " ?c tracker:notify true "
610 " FILTER regex (?c, \"%s\", \"i\") "
611 "}",
612 list_notifies);
613 }
614
615 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
616 g_free (query);
617
618 if (error) {
619 g_printerr ("%s, %s\n",
620 _("Could not find notify classes"),
621 error->message);
622 g_error_free (error);
623 g_object_unref (connection);
624
625 return EXIT_FAILURE;
626 }
627
628 print_cursor (cursor, _("No notifies were found"), _("Notifies"), TRUE);
629 }
630
631 if (list_indexes) {
632 gchar *query;
633
634 /* First list classes */
635 if (*list_indexes == '\0') {
636 query = g_strdup_printf ("SELECT ?p "
637 "WHERE {"
638 " ?p tracker:indexed true ."
639 "}");
640 } else {
641 query = g_strdup_printf ("SELECT ?p "
642 "WHERE {"
643 " ?p tracker:indexed true "
644 " FILTER regex (?p, \"%s\", \"i\") "
645 "}",
646 list_indexes);
647 }
648
649 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
650 g_free (query);
651
652 if (error) {
653 g_printerr ("%s, %s\n",
654 _("Could not find indexed properties"),
655 error->message);
656 g_error_free (error);
657 g_object_unref (connection);
658
659 return EXIT_FAILURE;
660 }
661
662 print_cursor (cursor, _("No indexes were found"), _("Indexes"), TRUE);
663 }
664
665 if (search) {
666 gchar *query;
667
668 /* First list classes */
669 query = g_strdup_printf ("SELECT ?c "
670 "WHERE {"
671 " ?c a rdfs:Class"
672 " FILTER regex (?c, \"%s\", \"i\") "
673 "}",
674 search);
675 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
676 g_free (query);
677
678 if (error) {
679 g_printerr ("%s, %s\n",
680 _("Could not search classes"),
681 error->message);
682 g_error_free (error);
683 g_object_unref (connection);
684
685 return EXIT_FAILURE;
686 }
687
688 print_cursor (cursor, _("No classes were found to match search term"), _("Classes"), TRUE);
689
690 /* Second list properties */
691 query = g_strdup_printf ("SELECT ?p "
692 "WHERE {"
693 " ?p a rdf:Property"
694 " FILTER regex (?p, \"%s\", \"i\") "
695 "}",
696 search);
697
698 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
699 g_free (query);
700
701 if (error) {
702 g_printerr ("%s, %s\n",
703 _("Could not search properties"),
704 error->message);
705 g_error_free (error);
706 g_object_unref (connection);
707
708 return EXIT_FAILURE;
709 }
710
711 print_cursor (cursor, _("No properties were found to match search term"), _("Properties"), TRUE);
712 }
713
714 if (file) {
715 gchar *path_in_utf8;
716 gsize size;
717
718 path_in_utf8 = g_filename_to_utf8 (file, -1, NULL, NULL, &error);
719 if (error) {
720 g_printerr ("%s:'%s', %s\n",
721 _("Could not get UTF-8 path from path"),
722 file,
723 error->message);
724 g_error_free (error);
725 g_object_unref (connection);
726
727 return EXIT_FAILURE;
728 }
729
730 g_file_get_contents (path_in_utf8, &query, &size, &error);
731 if (error) {
732 g_printerr ("%s:'%s', %s\n",
733 _("Could not read file"),
734 path_in_utf8,
735 error->message);
736 g_error_free (error);
737 g_free (path_in_utf8);
738 g_object_unref (connection);
739
740 return EXIT_FAILURE;
741 }
742
743 g_free (path_in_utf8);
744 }
745
746 if (query) {
747 if (G_UNLIKELY (update)) {
748 tracker_sparql_connection_update (connection, query, 0, NULL, &error);
749
750 if (error) {
751 g_printerr ("%s, %s\n",
752 _("Could not run update"),
753 error->message);
754 g_error_free (error);
755
756 return EXIT_FAILURE;
757 }
758
759 g_print ("%s\n", _("Done"));
760
761 #if 0
762 if (results) {
763 GPtrArray *insert;
764 GHashTable *solution;
765 GHashTableIter iter;
766 gpointer key, value;
767 gint i, s, n;
768
769 for (i = 0; i < results->len; i++) {
770 insert = results->pdata[i];
771
772 for (s = 0; s < insert->len; s++) {
773 solution = insert->pdata[s];
774
775 g_hash_table_iter_init (&iter, solution);
776 n = 0;
777 while (g_hash_table_iter_next (&iter, &key, &value)) {
778 g_print ("%s%s: %s",
779 n > 0 ? ", " : "",
780 (const gchar *) key,
781 (const gchar *) value);
782 n++;
783 }
784 g_print ("\n");
785 }
786 }
787 }
788 #endif
789 } else {
790 GHashTable *prefixes = NULL;
791
792 if (strstr (query, "fts:offsets")) {
793 prefixes = get_prefixes (connection);
794 }
795
796 cursor = tracker_sparql_connection_query (connection, query, NULL, &error);
797
798 if (error) {
799 g_printerr ("%s, %s\n",
800 _("Could not run query"),
801 error->message);
802 g_error_free (error);
803
804 if (prefixes) {
805 g_hash_table_unref (prefixes);
806 }
807
808 return EXIT_FAILURE;
809 }
810
811 if (G_UNLIKELY (prefixes)) {
812 print_cursor_with_ftsoffsets (cursor, prefixes, _("No results found matching your query"), _("Results"), FALSE);
813 g_hash_table_unref (prefixes);
814 } else {
815 print_cursor (cursor, _("No results found matching your query"), _("Results"), FALSE);
816 }
817 }
818 }
819
820 g_object_unref (connection);
821
822 return EXIT_SUCCESS;
823 }