No issues found
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3 * Copyright (C) 2005 Mr Jamie McCracken
4 *
5 * Nautilus is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * Nautilus 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 General Public
16 * License along with this program; see the file COPYING. If not,
17 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 *
20 * Author: Jamie McCracken <jamiemcc@gnome.org>
21 *
22 */
23
24 #include <config.h>
25 #include "nautilus-search-hit.h"
26 #include "nautilus-search-provider.h"
27 #include "nautilus-search-engine-tracker.h"
28 #include <string.h>
29 #include <gio/gio.h>
30
31 #include <libtracker-sparql/tracker-sparql.h>
32
33 struct NautilusSearchEngineTrackerDetails {
34 TrackerSparqlConnection *connection;
35 NautilusQuery *query;
36
37 gboolean query_pending;
38 GQueue *hits_pending;
39
40 GCancellable *cancellable;
41 };
42
43 static void nautilus_search_provider_init (NautilusSearchProviderIface *iface);
44
45 G_DEFINE_TYPE_WITH_CODE (NautilusSearchEngineTracker,
46 nautilus_search_engine_tracker,
47 G_TYPE_OBJECT,
48 G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SEARCH_PROVIDER,
49 nautilus_search_provider_init))
50
51 static void
52 finalize (GObject *object)
53 {
54 NautilusSearchEngineTracker *tracker;
55
56 tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (object);
57
58 if (tracker->details->cancellable) {
59 g_cancellable_cancel (tracker->details->cancellable);
60 g_clear_object (&tracker->details->cancellable);
61 }
62
63 g_clear_object (&tracker->details->query);
64 g_clear_object (&tracker->details->connection);
65 g_queue_free_full (tracker->details->hits_pending, g_object_unref);
66
67 G_OBJECT_CLASS (nautilus_search_engine_tracker_parent_class)->finalize (object);
68 }
69
70 #define BATCH_SIZE 100
71
72 static void
73 check_pending_hits (NautilusSearchEngineTracker *tracker,
74 gboolean force_send)
75 {
76 GList *hits = NULL;
77 NautilusSearchHit *hit;
78
79 if (!force_send &&
80 g_queue_get_length (tracker->details->hits_pending) < BATCH_SIZE) {
81 return;
82 }
83
84 while ((hit = g_queue_pop_head (tracker->details->hits_pending))) {
85 hits = g_list_prepend (hits, hit);
86 }
87
88 nautilus_search_provider_hits_added (NAUTILUS_SEARCH_PROVIDER (tracker), hits);
89 g_list_free_full (hits, g_object_unref);
90 }
91
92 static void
93 search_finished (NautilusSearchEngineTracker *tracker,
94 GError *error)
95 {
96 if (error == NULL) {
97 check_pending_hits (tracker, TRUE);
98 } else {
99 g_queue_foreach (tracker->details->hits_pending, (GFunc) g_object_unref, NULL);
100 g_queue_clear (tracker->details->hits_pending);
101 }
102
103 tracker->details->query_pending = FALSE;
104
105 if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
106 nautilus_search_provider_error (NAUTILUS_SEARCH_PROVIDER (tracker), error->message);
107 } else {
108 nautilus_search_provider_finished (NAUTILUS_SEARCH_PROVIDER (tracker));
109 }
110
111 g_object_unref (tracker);
112 }
113
114 static void cursor_callback (GObject *object,
115 GAsyncResult *result,
116 gpointer user_data);
117
118 static void
119 cursor_next (NautilusSearchEngineTracker *tracker,
120 TrackerSparqlCursor *cursor)
121 {
122 tracker_sparql_cursor_next_async (cursor,
123 tracker->details->cancellable,
124 cursor_callback,
125 tracker);
126 }
127
128 static void
129 cursor_callback (GObject *object,
130 GAsyncResult *result,
131 gpointer user_data)
132 {
133 NautilusSearchEngineTracker *tracker;
134 GError *error = NULL;
135 TrackerSparqlCursor *cursor;
136 NautilusSearchHit *hit;
137 const char *uri;
138 const char *mtime_str;
139 const char *atime_str;
140 GTimeVal tv;
141 gdouble rank, match;
142 gboolean success;
143 gchar *basename;
144
145 tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (user_data);
146
147 cursor = TRACKER_SPARQL_CURSOR (object);
148 success = tracker_sparql_cursor_next_finish (cursor, result, &error);
149
150 if (!success) {
151 search_finished (tracker, error);
152
153 g_clear_error (&error);
154 g_clear_object (&cursor);
155
156 return;
157 }
158
159 /* We iterate result by result, not n at a time. */
160 uri = tracker_sparql_cursor_get_string (cursor, 0, NULL);
161 rank = tracker_sparql_cursor_get_double (cursor, 1);
162 mtime_str = tracker_sparql_cursor_get_string (cursor, 2, NULL);
163 atime_str = tracker_sparql_cursor_get_string (cursor, 3, NULL);
164 basename = g_path_get_basename (uri);
165
166 hit = nautilus_search_hit_new (uri);
167 match = nautilus_query_matches_string (tracker->details->query, basename);
168 nautilus_search_hit_set_fts_rank (hit, rank + match);
169 g_free (basename);
170
171 if (g_time_val_from_iso8601 (mtime_str, &tv)) {
172 GDateTime *dt;
173 dt = g_date_time_new_from_timeval_local (&tv);
174 nautilus_search_hit_set_modification_time (hit, dt);
175 g_date_time_unref (dt);
176 } else {
177 g_warning ("unable to parse mtime: %s", mtime_str);
178 }
179 if (g_time_val_from_iso8601 (atime_str, &tv)) {
180 GDateTime *dt;
181 dt = g_date_time_new_from_timeval_local (&tv);
182 nautilus_search_hit_set_access_time (hit, dt);
183 g_date_time_unref (dt);
184 } else {
185 g_warning ("unable to parse atime: %s", atime_str);
186 }
187
188 g_queue_push_head (tracker->details->hits_pending, hit);
189 check_pending_hits (tracker, FALSE);
190
191 /* Get next */
192 cursor_next (tracker, cursor);
193 }
194
195 static void
196 query_callback (GObject *object,
197 GAsyncResult *result,
198 gpointer user_data)
199 {
200 NautilusSearchEngineTracker *tracker;
201 TrackerSparqlConnection *connection;
202 TrackerSparqlCursor *cursor;
203 GError *error = NULL;
204
205 tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (user_data);
206
207 connection = TRACKER_SPARQL_CONNECTION (object);
208 cursor = tracker_sparql_connection_query_finish (connection,
209 result,
210 &error);
211
212 if (error != NULL) {
213 search_finished (tracker, error);
214 g_error_free (error);
215 } else {
216 cursor_next (tracker, cursor);
217 }
218 }
219
220 static gboolean
221 search_finished_idle (gpointer user_data)
222 {
223 NautilusSearchEngineTracker *tracker = user_data;
224
225 search_finished (tracker, NULL);
226
227 return FALSE;
228 }
229
230 static void
231 nautilus_search_engine_tracker_start (NautilusSearchProvider *provider)
232 {
233 NautilusSearchEngineTracker *tracker;
234 gchar *query_text, *search_text, *location_uri, *downcase;
235 GString *sparql;
236 GList *mimetypes, *l;
237 gint mime_count;
238
239 tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
240
241 if (tracker->details->query_pending) {
242 return;
243 }
244
245 g_object_ref (tracker);
246 tracker->details->query_pending = TRUE;
247
248 if (tracker->details->connection == NULL) {
249 g_idle_add (search_finished_idle, provider);
250 return;
251 }
252
253 query_text = nautilus_query_get_text (tracker->details->query);
254 downcase = g_utf8_strdown (query_text, -1);
255 search_text = tracker_sparql_escape_string (downcase);
256 g_free (query_text);
257 g_free (downcase);
258
259 location_uri = nautilus_query_get_location (tracker->details->query);
260 mimetypes = nautilus_query_get_mime_types (tracker->details->query);
261
262 mime_count = g_list_length (mimetypes);
263
264 sparql = g_string_new ("SELECT DISTINCT nie:url(?urn) fts:rank(?urn) tracker:coalesce(nfo:fileLastModified(?urn), nie:contentLastModified(?urn)) AS ?mtime tracker:coalesce(nfo:fileLastAccessed(?urn), nie:contentAccessed(?urn)) AS ?atime "
265 "WHERE {"
266 " ?urn a nfo:FileDataObject ;"
267 " tracker:available true ; ");
268
269 if (mime_count > 0) {
270 g_string_append (sparql, "nie:mimeType ?mime ;");
271 }
272
273 g_string_append_printf (sparql,
274 " fts:match '\"%s*\"' . FILTER ("
275 " tracker:uri-is-descendant('%s', nie:url(?urn)) &&"
276 " fn:contains(fn:lower-case(nfo:fileName(?urn)), '%s')",
277 search_text, location_uri, search_text);
278
279 if (mime_count > 0) {
280 g_string_append (sparql, " && (");
281
282 for (l = mimetypes; l != NULL; l = l->next) {
283 if (l != mimetypes) {
284 g_string_append (sparql, " || ");
285 }
286
287 g_string_append_printf (sparql, "fn:contains(?mime, '%s')",
288 (gchar *) l->data);
289 }
290 g_string_append (sparql, ")");
291 }
292
293 g_string_append (sparql, ")} ORDER BY DESC (fts:rank(?urn))");
294
295 tracker->details->cancellable = g_cancellable_new ();
296 tracker_sparql_connection_query_async (tracker->details->connection,
297 sparql->str,
298 tracker->details->cancellable,
299 query_callback,
300 tracker);
301 g_string_free (sparql, TRUE);
302
303 g_free (search_text);
304 g_free (location_uri);
305 g_list_free_full (mimetypes, g_free);
306 }
307
308 static void
309 nautilus_search_engine_tracker_stop (NautilusSearchProvider *provider)
310 {
311 NautilusSearchEngineTracker *tracker;
312
313 tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
314
315 if (tracker->details->query_pending) {
316 g_cancellable_cancel (tracker->details->cancellable);
317 g_clear_object (&tracker->details->cancellable);
318 tracker->details->query_pending = FALSE;
319 }
320 }
321
322 static void
323 nautilus_search_engine_tracker_set_query (NautilusSearchProvider *provider,
324 NautilusQuery *query)
325 {
326 NautilusSearchEngineTracker *tracker;
327
328 tracker = NAUTILUS_SEARCH_ENGINE_TRACKER (provider);
329
330 g_object_ref (query);
331 g_clear_object (&tracker->details->query);
332 tracker->details->query = query;
333 }
334
335 static void
336 nautilus_search_provider_init (NautilusSearchProviderIface *iface)
337 {
338 iface->set_query = nautilus_search_engine_tracker_set_query;
339 iface->start = nautilus_search_engine_tracker_start;
340 iface->stop = nautilus_search_engine_tracker_stop;
341 }
342
343 static void
344 nautilus_search_engine_tracker_class_init (NautilusSearchEngineTrackerClass *class)
345 {
346 GObjectClass *gobject_class;
347
348 gobject_class = G_OBJECT_CLASS (class);
349 gobject_class->finalize = finalize;
350
351 g_type_class_add_private (class, sizeof (NautilusSearchEngineTrackerDetails));
352 }
353
354 static void
355 nautilus_search_engine_tracker_init (NautilusSearchEngineTracker *engine)
356 {
357 GError *error = NULL;
358
359 engine->details = G_TYPE_INSTANCE_GET_PRIVATE (engine, NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER,
360 NautilusSearchEngineTrackerDetails);
361 engine->details->hits_pending = g_queue_new ();
362
363 engine->details->connection = tracker_sparql_connection_get (NULL, &error);
364
365 if (error) {
366 g_warning ("Could not establish a connection to Tracker: %s", error->message);
367 g_error_free (error);
368 }
369 }
370
371
372 NautilusSearchEngineTracker *
373 nautilus_search_engine_tracker_new (void)
374 {
375 return g_object_new (NAUTILUS_TYPE_SEARCH_ENGINE_TRACKER, NULL);
376 }