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