1 /*
2  * Copyright (C) 2005 Novell, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser 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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Author: Anders Carlsson <andersca@imendio.com>
18  *
19  * Based on nautilus-search-engine.c
20  */
21 
22 #include "config.h"
23 #include "gtksearchengine.h"
24 #include "gtksearchenginesimple.h"
25 #include "gtksearchenginemodel.h"
26 #include "gtksearchenginequartz.h"
27 #include "gtkintl.h"
28 
29 #if defined(HAVE_TRACKER3)
30 #include "gtksearchenginetracker3.h"
31 #endif
32 #if !defined G_OS_WIN32 /* No tracker on windows */
33 #include "gtksearchenginetracker.h"
34 #define HAVE_TRACKER 1
35 #endif
36 
37 #include <gdk/gdk.h> /* for GDK_WINDOWING_QUARTZ */
38 
39 struct _GtkSearchEnginePrivate {
40   GtkSearchEngine *native;
41   gboolean native_running;
42   gchar *native_error;
43 
44   GtkSearchEngine *simple;
45   gboolean simple_running;
46   gboolean got_results;
47   gchar *simple_error;
48 
49   GtkSearchEngine *model;
50   gboolean model_running;
51   gchar *model_error;
52 
53   gboolean running;
54   gboolean recursive;
55   GHashTable *hits;
56 
57   GtkQuery *query;
58 };
59 
60 enum
61 {
62   HITS_ADDED,
63   FINISHED,
64   ERROR,
65   LAST_SIGNAL
66 };
67 
68 static guint signals[LAST_SIGNAL];
69 
70 G_DEFINE_TYPE_WITH_PRIVATE (GtkSearchEngine, _gtk_search_engine, G_TYPE_OBJECT);
71 
72 static void
set_query(GtkSearchEngine * engine,GtkQuery * query)73 set_query (GtkSearchEngine *engine,
74            GtkQuery        *query)
75 {
76   g_set_object (&engine->priv->query, query);
77 
78   if (engine->priv->native)
79     _gtk_search_engine_set_query (engine->priv->native, query);
80 
81   if (engine->priv->simple)
82     _gtk_search_engine_set_query (engine->priv->simple, query);
83 
84   if (engine->priv->model)
85     _gtk_search_engine_set_query (engine->priv->model, query);
86 }
87 
88 static void
start(GtkSearchEngine * engine)89 start (GtkSearchEngine *engine)
90 {
91   g_hash_table_remove_all (engine->priv->hits);
92 
93   if (engine->priv->native)
94     {
95       g_clear_pointer (&engine->priv->native_error, g_free);
96       _gtk_search_engine_start (engine->priv->native);
97       engine->priv->native_running = TRUE;
98     }
99 
100   if (engine->priv->simple)
101     {
102       g_clear_pointer (&engine->priv->simple_error, g_free);
103       _gtk_search_engine_start (engine->priv->simple);
104       engine->priv->simple_running = TRUE;
105     }
106 
107   if (engine->priv->model)
108     {
109       g_clear_pointer (&engine->priv->model_error, g_free);
110       _gtk_search_engine_start (engine->priv->model);
111       engine->priv->model_running = TRUE;
112     }
113 
114   engine->priv->running = TRUE;
115 }
116 
117 static void
stop(GtkSearchEngine * engine)118 stop (GtkSearchEngine *engine)
119 {
120   if (engine->priv->native)
121     {
122       _gtk_search_engine_stop (engine->priv->native);
123       engine->priv->native_running = FALSE;
124     }
125 
126   if (engine->priv->simple)
127     {
128       _gtk_search_engine_stop (engine->priv->simple);
129       engine->priv->simple_running = FALSE;
130     }
131 
132   if (engine->priv->model)
133     {
134       _gtk_search_engine_stop (engine->priv->model);
135       engine->priv->model_running = FALSE;
136     }
137 
138   engine->priv->running = FALSE;
139 
140   g_hash_table_remove_all (engine->priv->hits);
141 }
142 
143 static void
finalize(GObject * object)144 finalize (GObject *object)
145 {
146   GtkSearchEngine *engine = GTK_SEARCH_ENGINE (object);
147 
148   g_clear_object (&engine->priv->native);
149   g_free (engine->priv->native_error);
150 
151   g_clear_object (&engine->priv->simple);
152   g_free (engine->priv->simple_error);
153 
154   g_clear_object (&engine->priv->model);
155   g_free (engine->priv->model_error);
156 
157   g_clear_pointer (&engine->priv->hits, g_hash_table_unref);
158 
159   g_clear_object (&engine->priv->query);
160 
161   G_OBJECT_CLASS (_gtk_search_engine_parent_class)->finalize (object);
162 }
163 
164 static void
_gtk_search_engine_class_init(GtkSearchEngineClass * class)165 _gtk_search_engine_class_init (GtkSearchEngineClass *class)
166 {
167   GObjectClass *object_class = G_OBJECT_CLASS (class);
168 
169   object_class->finalize = finalize;
170 
171   class->set_query = set_query;
172   class->start = start;
173   class->stop = stop;
174 
175   signals[HITS_ADDED] =
176     g_signal_new (I_("hits-added"),
177                   G_TYPE_FROM_CLASS (class),
178                   G_SIGNAL_RUN_LAST,
179                   G_STRUCT_OFFSET (GtkSearchEngineClass, hits_added),
180                   NULL, NULL,
181                   NULL,
182                   G_TYPE_NONE, 1,
183                   G_TYPE_POINTER);
184 
185   signals[FINISHED] =
186     g_signal_new (I_("finished"),
187                   G_TYPE_FROM_CLASS (class),
188                   G_SIGNAL_RUN_LAST,
189                   G_STRUCT_OFFSET (GtkSearchEngineClass, finished),
190                   NULL, NULL,
191                   NULL,
192                   G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
193 
194   signals[ERROR] =
195     g_signal_new (I_("error"),
196                   G_TYPE_FROM_CLASS (class),
197                   G_SIGNAL_RUN_LAST,
198                   G_STRUCT_OFFSET (GtkSearchEngineClass, error),
199                   NULL, NULL,
200                   NULL,
201                   G_TYPE_NONE, 1,
202                   G_TYPE_STRING);
203 }
204 
205 static void
_gtk_search_engine_init(GtkSearchEngine * engine)206 _gtk_search_engine_init (GtkSearchEngine *engine)
207 {
208   engine->priv = _gtk_search_engine_get_instance_private (engine);
209 
210   engine->priv->recursive = TRUE;
211 }
212 
213 static void
hits_added(GtkSearchEngine * engine,GList * hits,gpointer data)214 hits_added (GtkSearchEngine *engine,
215             GList           *hits,
216             gpointer         data)
217 {
218   GtkSearchEngine *composite = GTK_SEARCH_ENGINE (data);
219   GList *added, *l;
220   GtkSearchHit *hit;
221 
222   added = NULL;
223 
224   for (l = hits; l; l = l->next)
225     {
226       hit = l->data;
227 
228       if (!g_hash_table_contains (composite->priv->hits, hit))
229         {
230           hit = _gtk_search_hit_dup (hit);
231           g_hash_table_add (composite->priv->hits, hit);
232           added = g_list_prepend (added, hit);
233         }
234     }
235 
236   if (added)
237     {
238       _gtk_search_engine_hits_added (composite, added);
239       g_list_free (added);
240     }
241 }
242 
243 static void
update_status(GtkSearchEngine * engine)244 update_status (GtkSearchEngine *engine)
245 {
246   gboolean running;
247 
248   running = engine->priv->native_running || engine->priv->simple_running;
249 
250   if (running != engine->priv->running)
251     {
252       engine->priv->running = running;
253 
254       if (!running)
255         {
256           if (engine->priv->native_error)
257             _gtk_search_engine_error (engine, engine->priv->native_error);
258           else if (engine->priv->simple_error)
259             _gtk_search_engine_error (engine, engine->priv->simple_error);
260           else if (engine->priv->model_error)
261             _gtk_search_engine_error (engine, engine->priv->model_error);
262           else
263             _gtk_search_engine_finished (engine, engine->priv->got_results);
264 
265           engine->priv->got_results = FALSE;
266         }
267     }
268 }
269 
270 static void
finished(GtkSearchEngine * engine,gboolean got_results,gpointer data)271 finished (GtkSearchEngine *engine,
272           gboolean         got_results,
273           gpointer         data)
274 {
275   GtkSearchEngine *composite = GTK_SEARCH_ENGINE (data);
276 
277   if (engine == composite->priv->native)
278     composite->priv->native_running = FALSE;
279   else if (engine == composite->priv->simple)
280     composite->priv->simple_running = FALSE;
281   else if (engine == composite->priv->model)
282     composite->priv->model_running = FALSE;
283 
284   composite->priv->got_results |= got_results;
285   update_status (composite);
286 }
287 
288 static void
error(GtkSearchEngine * engine,const gchar * message,gpointer data)289 error (GtkSearchEngine *engine,
290        const gchar     *message,
291        gpointer         data)
292 {
293   GtkSearchEngine *composite = GTK_SEARCH_ENGINE (data);
294 
295   if (engine == composite->priv->native)
296     {
297       g_free (composite->priv->native_error);
298       composite->priv->native_error = g_strdup (message);
299       composite->priv->native_running = FALSE;
300     }
301   else if (engine == composite->priv->simple)
302     {
303       g_free (composite->priv->simple_error);
304       composite->priv->simple_error = g_strdup (message);
305       composite->priv->simple_running = FALSE;
306     }
307   else if (engine == composite->priv->model)
308     {
309       g_free (composite->priv->model_error);
310       composite->priv->model_error = g_strdup (message);
311       composite->priv->model_running = FALSE;
312     }
313 
314   update_status (composite);
315 }
316 
317 static gboolean
search_hit_equal(gconstpointer a,gconstpointer b)318 search_hit_equal (gconstpointer a, gconstpointer b)
319 {
320   const GtkSearchHit *ha = (const GtkSearchHit *)a;
321   const GtkSearchHit *hb = (const GtkSearchHit *)b;
322 
323   return g_file_equal (ha->file, hb->file);
324 }
325 
326 
327 static guint
search_hit_hash(gconstpointer a)328 search_hit_hash (gconstpointer a)
329 {
330   const GtkSearchHit *ha = (const GtkSearchHit *)a;
331 
332   return g_file_hash (ha->file);
333 }
334 
335 GtkSearchHit *
_gtk_search_hit_dup(GtkSearchHit * hit)336 _gtk_search_hit_dup (GtkSearchHit *hit)
337 {
338   GtkSearchHit *dup;
339 
340   dup = g_new (GtkSearchHit, 1);
341   dup->file = g_object_ref (hit->file);
342   if (hit->info)
343     dup->info = g_object_ref (hit->info);
344   else
345     dup->info = NULL;
346 
347   return dup;
348 }
349 
350 void
_gtk_search_hit_free(GtkSearchHit * hit)351 _gtk_search_hit_free (GtkSearchHit *hit)
352 {
353   g_clear_object (&hit->file);
354   g_clear_object (&hit->info);
355   g_free (hit);
356 }
357 
358 static void
connect_engine_signals(GtkSearchEngine * engine,gpointer data)359 connect_engine_signals (GtkSearchEngine *engine,
360                         gpointer         data)
361 {
362   g_signal_connect_object (engine, "hits-added", G_CALLBACK (hits_added), data, 0);
363   g_signal_connect_object (engine, "finished", G_CALLBACK (finished), data, 0);
364   g_signal_connect_object (engine, "error", G_CALLBACK (error), data, 0);
365 }
366 
367 GtkSearchEngine *
_gtk_search_engine_new(void)368 _gtk_search_engine_new (void)
369 {
370   GtkSearchEngine *engine;
371 
372   engine = g_object_new (GTK_TYPE_SEARCH_ENGINE, NULL);
373 
374   engine->priv->simple = _gtk_search_engine_simple_new ();
375   g_debug ("Using simple search engine");
376   connect_engine_signals (engine->priv->simple, engine);
377 
378 #if defined(HAVE_TRACKER3)
379   engine->priv->native = gtk_search_engine_tracker3_new ();
380   if (engine->priv->native)
381     {
382       g_debug ("Using Tracker3 search engine");
383       connect_engine_signals (engine->priv->native, engine);
384       _gtk_search_engine_simple_set_indexed_cb (GTK_SEARCH_ENGINE_SIMPLE (engine->priv->simple),
385                                                 gtk_search_engine_tracker3_is_indexed,
386                                                 g_object_ref (engine->priv->native),
387                                                 g_object_unref);
388     }
389 #endif
390 
391 #ifdef HAVE_TRACKER
392   if (!engine->priv->native)
393     {
394       engine->priv->native = _gtk_search_engine_tracker_new ();
395       if (engine->priv->native)
396         {
397           g_debug ("Using Tracker search engine");
398           connect_engine_signals (engine->priv->native, engine);
399           _gtk_search_engine_simple_set_indexed_cb (GTK_SEARCH_ENGINE_SIMPLE (engine->priv->simple),
400                                                     _gtk_search_engine_tracker_is_indexed,
401                                                     g_object_ref (engine->priv->native),
402                                                     g_object_unref);
403         }
404     }
405 #endif
406 
407 #ifdef GDK_WINDOWING_QUARTZ
408   engine->priv->native = _gtk_search_engine_quartz_new ();
409   if (engine->priv->native)
410     {
411       g_debug ("Using Quartz search engine");
412       connect_engine_signals (engine->priv->native, engine);
413     }
414 #endif
415 
416   engine->priv->hits = g_hash_table_new_full (search_hit_hash, search_hit_equal,
417                                               (GDestroyNotify)_gtk_search_hit_free, NULL);
418 
419   return engine;
420 }
421 
422 void
_gtk_search_engine_set_query(GtkSearchEngine * engine,GtkQuery * query)423 _gtk_search_engine_set_query (GtkSearchEngine *engine,
424                               GtkQuery        *query)
425 {
426   g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
427   g_return_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->set_query != NULL);
428 
429   GTK_SEARCH_ENGINE_GET_CLASS (engine)->set_query (engine, query);
430 }
431 
432 void
_gtk_search_engine_start(GtkSearchEngine * engine)433 _gtk_search_engine_start (GtkSearchEngine *engine)
434 {
435   g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
436   g_return_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->start != NULL);
437 
438   GTK_SEARCH_ENGINE_GET_CLASS (engine)->start (engine);
439 }
440 
441 void
_gtk_search_engine_stop(GtkSearchEngine * engine)442 _gtk_search_engine_stop (GtkSearchEngine *engine)
443 {
444   g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
445   g_return_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->stop != NULL);
446 
447   GTK_SEARCH_ENGINE_GET_CLASS (engine)->stop (engine);
448 }
449 
450 void
_gtk_search_engine_hits_added(GtkSearchEngine * engine,GList * hits)451 _gtk_search_engine_hits_added (GtkSearchEngine *engine,
452                                GList           *hits)
453 {
454   g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
455 
456   g_signal_emit (engine, signals[HITS_ADDED], 0, hits);
457 }
458 
459 void
_gtk_search_engine_finished(GtkSearchEngine * engine,gboolean got_results)460 _gtk_search_engine_finished (GtkSearchEngine *engine,
461                              gboolean         got_results)
462 {
463   g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
464 
465   g_signal_emit (engine, signals[FINISHED], 0, got_results);
466 }
467 
468 void
_gtk_search_engine_error(GtkSearchEngine * engine,const gchar * error_message)469 _gtk_search_engine_error (GtkSearchEngine *engine,
470                           const gchar     *error_message)
471 {
472   g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
473 
474   g_signal_emit (engine, signals[ERROR], 0, error_message);
475 }
476 
477 void
_gtk_search_engine_set_recursive(GtkSearchEngine * engine,gboolean recursive)478 _gtk_search_engine_set_recursive (GtkSearchEngine *engine,
479                                   gboolean         recursive)
480 {
481   g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
482 
483   g_assert (!engine->priv->running);
484 
485   engine->priv->recursive = recursive;
486 
487   if (engine->priv->native)
488     _gtk_search_engine_set_recursive (engine->priv->native, recursive);
489 
490   if (engine->priv->simple)
491     _gtk_search_engine_set_recursive (engine->priv->simple, recursive);
492 }
493 
494 gboolean
_gtk_search_engine_get_recursive(GtkSearchEngine * engine)495 _gtk_search_engine_get_recursive (GtkSearchEngine *engine)
496 {
497   g_return_val_if_fail (GTK_IS_SEARCH_ENGINE (engine), TRUE);
498 
499   return engine->priv->recursive;
500 }
501 
502 void
_gtk_search_engine_set_model(GtkSearchEngine * engine,GtkFileSystemModel * model)503 _gtk_search_engine_set_model (GtkSearchEngine    *engine,
504                               GtkFileSystemModel *model)
505 {
506   g_clear_object (&engine->priv->model);
507   if (model)
508     {
509       engine->priv->model = _gtk_search_engine_model_new (model);
510       connect_engine_signals (engine->priv->model, engine);
511       if (engine->priv->query)
512         _gtk_search_engine_set_query (engine->priv->model, engine->priv->query);
513     }
514 }
515