1 /*
2 * Copyright (C) 2020 Red Hat Inc
3 * Copyright (C) 2009-2011 Nokia <ivan.frade@nokia.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authors: Carlos Garnacho <carlosg@gnome.org>
19 * Jürg Billeter <juerg.billeter@codethink.co.uk>
20 * Martyn Russell <martyn@lanedo.com>
21 *
22 * Based on nautilus-search-engine-tracker.c
23 */
24
25 #include "config.h"
26
27 #include <string.h>
28
29 #include <gio/gio.h>
30 #include <gmodule.h>
31 #include <gdk/gdk.h>
32 #include <gtk/gtk.h>
33 #include <libtracker-sparql/tracker-sparql.h>
34
35 #include "gtksearchenginetracker3.h"
36
37 #define MINER_FS_BUS_NAME "org.freedesktop.Tracker3.Miner.Files"
38
39 #define SEARCH_QUERY_BASE(__PATTERN__) \
40 "SELECT ?url " \
41 " nfo:fileName(?urn) " \
42 " nie:mimeType(?urn)" \
43 " nfo:fileSize(?urn)" \
44 " nfo:fileLastModified(?urn)" \
45 "FROM tracker:FileSystem " \
46 "WHERE {" \
47 " ?urn a nfo:FileDataObject ;" \
48 " nie:url ?url ; " \
49 " fts:match ~match . " \
50 __PATTERN__ \
51 "} " \
52 "ORDER BY DESC(fts:rank(?urn)) DESC(?url)"
53
54 #define SEARCH_QUERY SEARCH_QUERY_BASE("")
55 #define SEARCH_RECURSIVE_QUERY SEARCH_QUERY_BASE("?urn (nfo:belongsToContainer/nie:isStoredAs)+/nie:url ~location")
56 #define SEARCH_LOCATION_QUERY SEARCH_QUERY_BASE("?urn nfo:belongsToContainer/nie:isStoredAs/nie:url ~location")
57 #define FILE_CHECK_QUERY "ASK { ?urn nie:url ~url }"
58
59 struct _GtkSearchEngineTracker3
60 {
61 GtkSearchEngine parent;
62 TrackerSparqlConnection *sparql_conn;
63 TrackerSparqlStatement *search_query;
64 TrackerSparqlStatement *search_recursive_query;
65 TrackerSparqlStatement *search_location_query;
66 TrackerSparqlStatement *file_check_query;
67 GCancellable *cancellable;
68 GtkQuery *query;
69 gboolean query_pending;
70 };
71
72 struct _GtkSearchEngineTracker3Class
73 {
74 GtkSearchEngineClass parent_class;
75 };
76
77 static void gtk_search_engine_tracker3_initable_iface_init (GInitableIface *iface);
78
G_DEFINE_TYPE_WITH_CODE(GtkSearchEngineTracker3,gtk_search_engine_tracker3,GTK_TYPE_SEARCH_ENGINE,G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,gtk_search_engine_tracker3_initable_iface_init))79 G_DEFINE_TYPE_WITH_CODE (GtkSearchEngineTracker3,
80 gtk_search_engine_tracker3,
81 GTK_TYPE_SEARCH_ENGINE,
82 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
83 gtk_search_engine_tracker3_initable_iface_init))
84
85 static void
86 finalize (GObject *object)
87 {
88 GtkSearchEngineTracker3 *engine;
89
90 g_debug ("Finalizing GtkSearchEngineTracker3");
91
92 engine = GTK_SEARCH_ENGINE_TRACKER3 (object);
93
94 if (engine->cancellable)
95 {
96 g_cancellable_cancel (engine->cancellable);
97 g_object_unref (engine->cancellable);
98 }
99
100 g_clear_object (&engine->search_query);
101 g_clear_object (&engine->search_location_query);
102 g_clear_object (&engine->file_check_query);
103 tracker_sparql_connection_close (engine->sparql_conn);
104 g_clear_object (&engine->sparql_conn);
105
106 G_OBJECT_CLASS (gtk_search_engine_tracker3_parent_class)->finalize (object);
107 }
108
109 static void
free_hit(gpointer data)110 free_hit (gpointer data)
111 {
112 GtkSearchHit *hit = data;
113
114 g_clear_object (&hit->file);
115 g_clear_object (&hit->info);
116 g_slice_free (GtkSearchHit, hit);
117 }
118
119 static GFileInfo *
create_file_info(TrackerSparqlCursor * cursor)120 create_file_info (TrackerSparqlCursor *cursor)
121 {
122 GFileInfo *info;
123 const gchar *str;
124 GDateTime *creation;
125
126 info = g_file_info_new ();
127 str = tracker_sparql_cursor_get_string (cursor, 1, NULL);
128 if (str)
129 g_file_info_set_display_name (info, str);
130
131 str = tracker_sparql_cursor_get_string (cursor, 2, NULL);
132 if (str)
133 g_file_info_set_content_type (info, str);
134
135 g_file_info_set_size (info,
136 tracker_sparql_cursor_get_integer (cursor, 3));
137
138 str = tracker_sparql_cursor_get_string (cursor, 4, NULL);
139 if (str)
140 {
141 creation = g_date_time_new_from_iso8601 (str, NULL);
142 g_file_info_set_modification_date_time (info, creation);
143 g_date_time_unref (creation);
144 }
145
146 return info;
147 }
148
149 static void
query_callback(TrackerSparqlStatement * statement,GAsyncResult * res,gpointer user_data)150 query_callback (TrackerSparqlStatement *statement,
151 GAsyncResult *res,
152 gpointer user_data)
153 {
154 GtkSearchEngineTracker3 *engine;
155 TrackerSparqlCursor *cursor;
156 GList *hits = NULL;
157 GError *error = NULL;
158 GtkSearchHit *hit;
159
160 engine = GTK_SEARCH_ENGINE_TRACKER3 (user_data);
161
162 engine->query_pending = FALSE;
163
164 cursor = tracker_sparql_statement_execute_finish (statement, res, &error);
165
166 if (!cursor)
167 {
168 _gtk_search_engine_error (GTK_SEARCH_ENGINE (engine), error->message);
169 g_error_free (error);
170 g_object_unref (engine);
171 return;
172 }
173
174 while (tracker_sparql_cursor_next (cursor, NULL, NULL))
175 {
176 const gchar *url;
177
178 url = tracker_sparql_cursor_get_string (cursor, 0, NULL);
179 hit = g_slice_new0 (GtkSearchHit);
180 hit->file = g_file_new_for_uri (url);
181 hit->info = create_file_info (cursor);
182 hits = g_list_prepend (hits, hit);
183 }
184
185 tracker_sparql_cursor_close (cursor);
186
187 _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (engine), hits);
188 _gtk_search_engine_finished (GTK_SEARCH_ENGINE (engine), hits != NULL);
189
190 g_list_free_full (hits, free_hit);
191 g_object_unref (engine);
192 g_object_unref (cursor);
193 }
194
195 static void
gtk_search_engine_tracker3_start(GtkSearchEngine * engine)196 gtk_search_engine_tracker3_start (GtkSearchEngine *engine)
197 {
198 GtkSearchEngineTracker3 *tracker;
199 TrackerSparqlStatement *statement;
200 const gchar *search_text;
201 gboolean recursive;
202 gchar *match;
203 GFile *location;
204
205 tracker = GTK_SEARCH_ENGINE_TRACKER3 (engine);
206
207 if (tracker->query_pending)
208 {
209 g_debug ("Attempt to start a new search while one is pending, doing nothing");
210 return;
211 }
212
213 if (tracker->query == NULL)
214 {
215 g_debug ("Attempt to start a new search with no GtkQuery, doing nothing");
216 return;
217 }
218
219 tracker->query_pending = TRUE;
220 search_text = gtk_query_get_text (tracker->query);
221 location = gtk_query_get_location (tracker->query);
222 recursive = _gtk_search_engine_get_recursive (engine);
223
224 if (location)
225 {
226 gchar *location_uri = g_file_get_uri (location);
227
228 if (recursive)
229 {
230 g_debug ("Recursive search query in location: %s", location_uri);
231 statement = tracker->search_recursive_query;
232 }
233 else
234 {
235 g_debug ("Search query in location: %s", location_uri);
236 statement = tracker->search_location_query;
237 }
238
239 tracker_sparql_statement_bind_string (statement,
240 "location",
241 location_uri);
242 g_free (location_uri);
243 }
244 else
245 {
246 g_debug ("Search query");
247 statement = tracker->search_query;
248 }
249
250 match = g_strdup_printf ("%s*", search_text);
251 tracker_sparql_statement_bind_string (statement, "match", match);
252 g_debug ("search text: %s\n", match);
253 tracker_sparql_statement_execute_async (statement, tracker->cancellable,
254 (GAsyncReadyCallback) query_callback,
255 g_object_ref (tracker));
256 g_free (match);
257 }
258
259 static void
gtk_search_engine_tracker3_stop(GtkSearchEngine * engine)260 gtk_search_engine_tracker3_stop (GtkSearchEngine *engine)
261 {
262 GtkSearchEngineTracker3 *tracker;
263
264 tracker = GTK_SEARCH_ENGINE_TRACKER3 (engine);
265
266 if (tracker->query && tracker->query_pending)
267 {
268 g_cancellable_cancel (tracker->cancellable);
269 tracker->query_pending = FALSE;
270 }
271 }
272
273 static void
gtk_search_engine_tracker3_set_query(GtkSearchEngine * engine,GtkQuery * query)274 gtk_search_engine_tracker3_set_query (GtkSearchEngine *engine,
275 GtkQuery *query)
276 {
277 GtkSearchEngineTracker3 *tracker;
278
279 tracker = GTK_SEARCH_ENGINE_TRACKER3 (engine);
280
281 if (query)
282 g_object_ref (query);
283
284 if (tracker->query)
285 g_object_unref (tracker->query);
286
287 tracker->query = query;
288 }
289
290 static void
gtk_search_engine_tracker3_class_init(GtkSearchEngineTracker3Class * class)291 gtk_search_engine_tracker3_class_init (GtkSearchEngineTracker3Class *class)
292 {
293 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
294 GtkSearchEngineClass *engine_class = GTK_SEARCH_ENGINE_CLASS (class);
295
296 gobject_class->finalize = finalize;
297
298 engine_class->set_query = gtk_search_engine_tracker3_set_query;
299 engine_class->start = gtk_search_engine_tracker3_start;
300 engine_class->stop = gtk_search_engine_tracker3_stop;
301 }
302
303 static void
gtk_search_engine_tracker3_init(GtkSearchEngineTracker3 * engine)304 gtk_search_engine_tracker3_init (GtkSearchEngineTracker3 *engine)
305 {
306 engine->cancellable = g_cancellable_new ();
307 engine->query_pending = FALSE;
308 }
309
310 static gboolean
gtk_search_engine_tracker3_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)311 gtk_search_engine_tracker3_initable_init (GInitable *initable,
312 GCancellable *cancellable,
313 GError **error)
314 {
315 GtkSearchEngineTracker3 *engine;
316
317 engine = GTK_SEARCH_ENGINE_TRACKER3 (initable);
318
319 engine->sparql_conn = tracker_sparql_connection_bus_new (MINER_FS_BUS_NAME,
320 NULL, NULL,
321 error);
322 if (!engine->sparql_conn)
323 return FALSE;
324
325 engine->search_query =
326 tracker_sparql_connection_query_statement (engine->sparql_conn,
327 SEARCH_QUERY,
328 cancellable,
329 error);
330 if (!engine->search_query)
331 return FALSE;
332
333 engine->search_recursive_query =
334 tracker_sparql_connection_query_statement (engine->sparql_conn,
335 SEARCH_RECURSIVE_QUERY,
336 cancellable,
337 error);
338 if (!engine->search_recursive_query)
339 return FALSE;
340
341 engine->search_location_query =
342 tracker_sparql_connection_query_statement (engine->sparql_conn,
343 SEARCH_LOCATION_QUERY,
344 cancellable,
345 error);
346 if (!engine->search_location_query)
347 return FALSE;
348
349 engine->file_check_query =
350 tracker_sparql_connection_query_statement (engine->sparql_conn,
351 FILE_CHECK_QUERY,
352 cancellable,
353 error);
354 if (!engine->file_check_query)
355 return FALSE;
356
357 return TRUE;
358 }
359
360 static void
gtk_search_engine_tracker3_initable_iface_init(GInitableIface * iface)361 gtk_search_engine_tracker3_initable_iface_init (GInitableIface *iface)
362 {
363 iface->init = gtk_search_engine_tracker3_initable_init;
364 }
365
366 GtkSearchEngine *
gtk_search_engine_tracker3_new(void)367 gtk_search_engine_tracker3_new (void)
368 {
369 GtkSearchEngineTracker3 *engine;
370 GError *error = NULL;
371 GModule *self;
372
373 self = g_module_open (NULL, G_MODULE_BIND_LAZY);
374
375 /* Avoid hell from breaking loose if the application links to Tracker 2.x */
376 if (self)
377 {
378 gpointer symbol;
379 gboolean found;
380
381 found = g_module_symbol (self, "tracker_sparql_builder_new", &symbol);
382 g_module_close (self);
383
384 if (found)
385 return NULL;
386 }
387
388 g_debug ("Creating GtkSearchEngineTracker3...");
389
390 engine = g_initable_new (GTK_TYPE_SEARCH_ENGINE_TRACKER3,
391 NULL, &error, NULL);
392 if (!engine)
393 {
394 g_critical ("Could not init tracker3 search engine: %s",
395 error->message);
396 g_error_free (error);
397 }
398
399 return GTK_SEARCH_ENGINE (engine);
400 }
401
402 gboolean
gtk_search_engine_tracker3_is_indexed(GFile * location,gpointer data)403 gtk_search_engine_tracker3_is_indexed (GFile *location,
404 gpointer data)
405 {
406 GtkSearchEngineTracker3 *engine = data;
407 TrackerSparqlCursor *cursor;
408 GError *error = NULL;
409 gboolean indexed;
410 gchar *uri;
411
412 uri = g_file_get_uri (location);
413 tracker_sparql_statement_bind_string (engine->file_check_query,
414 "url", uri);
415 cursor = tracker_sparql_statement_execute (engine->file_check_query,
416 engine->cancellable, &error);
417 g_free (uri);
418
419 if (!cursor ||
420 !tracker_sparql_cursor_next (cursor, NULL, NULL))
421 {
422 g_warning ("Error checking indexed file '%s': %s",
423 uri, error->message);
424 g_error_free (error);
425 g_free (uri);
426 return FALSE;
427 }
428
429 indexed = tracker_sparql_cursor_get_boolean (cursor, 0);
430 tracker_sparql_cursor_close (cursor);
431 g_object_unref (cursor);
432
433 return indexed;
434 }
435