1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3 * Copyright (C) 2005 Red Hat, Inc
4 *
5 * Nemo 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 * Nemo 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., 51 Franklin Street - Suite 500,
18 * Boston, MA 02110-1335, USA.
19 *
20 * Author: Alexander Larsson <alexl@redhat.com>
21 *
22 */
23
24 #include <config.h>
25 #include "nemo-search-engine-simple.h"
26
27 #include <string.h>
28 #include <glib.h>
29 #include <gio/gio.h>
30
31 #define BATCH_SIZE 500
32
33 typedef struct {
34 NemoSearchEngineSimple *engine;
35 GCancellable *cancellable;
36
37 GList *mime_types;
38 char **words;
39 gboolean *word_strstr;
40 gboolean words_and;
41
42 GList *found_list;
43
44 GQueue *directories; /* GFiles */
45
46 GHashTable *visited;
47
48 gint n_processed_files;
49 GList *uri_hits;
50
51 gboolean show_hidden;
52 } SearchThreadData;
53
54
55 struct NemoSearchEngineSimpleDetails {
56 NemoQuery *query;
57
58 SearchThreadData *active_search;
59
60 gboolean query_finished;
61 };
62
63 G_DEFINE_TYPE (NemoSearchEngineSimple, nemo_search_engine_simple,
64 NEMO_TYPE_SEARCH_ENGINE);
65
66 static void
finalize(GObject * object)67 finalize (GObject *object)
68 {
69 NemoSearchEngineSimple *simple;
70
71 simple = NEMO_SEARCH_ENGINE_SIMPLE (object);
72
73 if (simple->details->query) {
74 g_object_unref (simple->details->query);
75 simple->details->query = NULL;
76 }
77
78 G_OBJECT_CLASS (nemo_search_engine_simple_parent_class)->finalize (object);
79 }
80
81 /**
82 * function modified taken from glib2 / gstrfuncs.c
83 */
84 static gchar**
strsplit_esc_n(const gchar * string,const gchar delimiter,const gchar escape,gint max_tokens,gint * n_tokens)85 strsplit_esc_n (const gchar *string,
86 const gchar delimiter,
87 const gchar escape,
88 gint max_tokens,
89 gint *n_tokens)
90 {
91 GSList *string_list = NULL, *slist;
92 gchar **str_array;
93 guint n = 0;
94 const gchar *remainder, *s;
95
96 g_return_val_if_fail (string != NULL, NULL);
97 g_return_val_if_fail (delimiter != '\0', NULL);
98
99 if (max_tokens < 1)
100 max_tokens = G_MAXINT;
101
102 remainder = string;
103 s = remainder;
104 while (s && *s) {
105 if (*s == delimiter) break;
106 else if (*s == escape) {
107 s++;
108 if (*s == 0) break;
109 }
110 s++;
111 }
112 if (*s == 0) s = NULL;
113 if (s) {
114 while (--max_tokens && s) {
115 gsize len;
116
117 len = s - remainder;
118 string_list = g_slist_prepend (string_list,
119 g_strndup (remainder, len));
120 n++;
121 remainder = s + 1;
122
123 s = remainder;
124 while (s && *s) {
125 if (*s == delimiter) break;
126 else if (*s == escape) {
127 s++;
128 if (*s == 0) break;
129 }
130 s++;
131 }
132 if (*s == 0) s = NULL;
133 }
134 }
135 if (*string) {
136 n++;
137 string_list = g_slist_prepend (string_list, g_strdup (remainder));
138 }
139 *n_tokens = n;
140 str_array = g_new (gchar*, n + 1);
141
142 str_array[n--] = NULL;
143 for (slist = string_list; slist; slist = slist->next)
144 str_array[n--] = slist->data;
145
146 g_slist_free (string_list);
147
148 return str_array;
149 }
150
151 static SearchThreadData *
search_thread_data_new(NemoSearchEngineSimple * engine,NemoQuery * query)152 search_thread_data_new (NemoSearchEngineSimple *engine,
153 NemoQuery *query)
154 {
155 SearchThreadData *data;
156 char *text, *lower, *normalized, *uri;
157 GFile *location;
158 gint n=1, i;
159
160 data = g_new0 (SearchThreadData, 1);
161
162 data->show_hidden = nemo_query_get_show_hidden (query);
163 data->engine = engine;
164 data->directories = g_queue_new ();
165 data->visited = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
166 uri = nemo_query_get_location (query);
167 location = NULL;
168 if (uri != NULL) {
169 location = g_file_new_for_uri (uri);
170 g_free (uri);
171 }
172 if (location == NULL) {
173 location = g_file_new_for_path ("/");
174 }
175 g_queue_push_tail (data->directories, location);
176
177 text = nemo_query_get_text (query);
178 normalized = g_utf8_normalize (text, -1, G_NORMALIZE_NFD);
179 lower = g_utf8_strdown (normalized, -1);
180 data->words = strsplit_esc_n (lower, ' ', '\\', -1, &n);
181 g_free (text);
182 g_free (lower);
183 g_free (normalized);
184
185 data->word_strstr = g_malloc(sizeof(gboolean)*n);
186 data->words_and = TRUE;
187 for (i = 0; data->words[i] != NULL; i++) {
188 data->word_strstr[i]=TRUE;
189 text = data->words[i];
190 while(*text!=0) {
191 if(*text=='\\' || *text=='?' || *text=='*') {
192 data->word_strstr[i]=FALSE;
193 break;
194 }
195 text++;
196 }
197 if (!data->word_strstr[i]) data->words_and = FALSE;
198 }
199
200 data->mime_types = nemo_query_get_mime_types (query);
201
202 data->cancellable = g_cancellable_new ();
203
204 return data;
205 }
206
207 static void
search_thread_data_free(SearchThreadData * data)208 search_thread_data_free (SearchThreadData *data)
209 {
210 g_queue_foreach (data->directories,
211 (GFunc)g_object_unref, NULL);
212 g_queue_free (data->directories);
213 g_hash_table_destroy (data->visited);
214 g_object_unref (data->cancellable);
215 g_strfreev (data->words);
216 g_free (data->word_strstr);
217 g_list_free_full (data->mime_types, g_free);
218 g_list_free_full (data->uri_hits, g_free);
219 g_free (data);
220 }
221
222 static gboolean
search_thread_done_idle(gpointer user_data)223 search_thread_done_idle (gpointer user_data)
224 {
225 SearchThreadData *data;
226
227 data = user_data;
228
229 if (!g_cancellable_is_cancelled (data->cancellable)) {
230 nemo_search_engine_finished (NEMO_SEARCH_ENGINE (data->engine));
231 data->engine->details->active_search = NULL;
232 }
233
234 search_thread_data_free (data);
235
236 return FALSE;
237 }
238
239 typedef struct {
240 GList *uris;
241 SearchThreadData *thread_data;
242 } SearchHits;
243
244
245 static gboolean
search_thread_add_hits_idle(gpointer user_data)246 search_thread_add_hits_idle (gpointer user_data)
247 {
248 SearchHits *hits;
249
250 hits = user_data;
251
252 if (!g_cancellable_is_cancelled (hits->thread_data->cancellable)) {
253 nemo_search_engine_hits_added (NEMO_SEARCH_ENGINE (hits->thread_data->engine),
254 hits->uris);
255 }
256
257 g_list_free_full (hits->uris, g_free);
258 g_free (hits);
259
260 return FALSE;
261 }
262
263 static void
send_batch(SearchThreadData * data)264 send_batch (SearchThreadData *data)
265 {
266 SearchHits *hits;
267
268 data->n_processed_files = 0;
269
270 if (data->uri_hits) {
271 hits = g_new (SearchHits, 1);
272 hits->uris = data->uri_hits;
273 hits->thread_data = data;
274 g_idle_add (search_thread_add_hits_idle, hits);
275 }
276 data->uri_hits = NULL;
277 }
278
279 static gboolean
strwildcardcmp(char * a,char * b)280 strwildcardcmp(char *a, char *b)
281 {
282 if (*a == 0 && *b == 0) return TRUE;
283 while(*a!=0 && *b!=0) {
284 if(*a=='\\') { // escaped character
285 a++;
286 if (*a != *b) return FALSE;
287 }
288 else {
289 if (*a=='*') {
290 if(*(a+1)==0) return TRUE;
291 if(*b==0) return FALSE;
292 if (strwildcardcmp(a+1, b) || strwildcardcmp(a, b+1)) return TRUE;
293 else return FALSE;
294 }
295 else if (*a!='?' && (*a != *b)) return FALSE;
296 }
297 a++;
298 b++;
299 }
300 if ((*a == 0 && *b == 0) || (*a=='*' && *(a+1)==0)) return TRUE;
301 return FALSE;
302 }
303
304 #define STD_ATTRIBUTES \
305 G_FILE_ATTRIBUTE_STANDARD_NAME "," \
306 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \
307 G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
308 G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
309 G_FILE_ATTRIBUTE_ID_FILE
310
311 static void
visit_directory(GFile * dir,SearchThreadData * data)312 visit_directory (GFile *dir, SearchThreadData *data)
313 {
314 GFileEnumerator *enumerator;
315 GFileInfo *info;
316 GFile *child;
317 const char *mime_type, *display_name;
318 char *lower_name, *normalized;
319 gboolean hit;
320 int i;
321 GList *l;
322 const char *id;
323 gboolean visited;
324
325 enumerator = g_file_enumerate_children (dir,
326 data->mime_types != NULL ?
327 STD_ATTRIBUTES ","
328 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
329 :
330 STD_ATTRIBUTES
331 ,
332 0, data->cancellable, NULL);
333
334 if (enumerator == NULL) {
335 return;
336 }
337
338 while ((info = g_file_enumerator_next_file (enumerator, data->cancellable, NULL)) != NULL) {
339 if (g_file_info_get_is_hidden (info) && !data->show_hidden) {
340 goto next;
341 }
342
343 display_name = g_file_info_get_display_name (info);
344 if (display_name == NULL) {
345 goto next;
346 }
347
348 normalized = g_utf8_normalize (display_name, -1, G_NORMALIZE_NFD);
349 lower_name = g_utf8_strdown (normalized, -1);
350 g_free (normalized);
351
352 hit = data->words_and;
353 for (i = 0; data->words[i] != NULL; i++) {
354 if (data->word_strstr[i]) {
355 if ((strstr (lower_name, data->words[i]) != NULL)^data->words_and) {
356 hit = !data->words_and;
357 break;
358 }
359 }
360 else if (strwildcardcmp (data->words[i], lower_name)^data->words_and) {
361 hit = !data->words_and;
362 break;
363 }
364 }
365 g_free (lower_name);
366
367 if (hit && data->mime_types) {
368 mime_type = g_file_info_get_content_type (info);
369 hit = FALSE;
370
371 for (l = data->mime_types; mime_type != NULL && l != NULL; l = l->next) {
372 if (g_content_type_equals (mime_type, l->data)) {
373 hit = TRUE;
374 break;
375 }
376 }
377 }
378
379 child = g_file_get_child (dir, g_file_info_get_name (info));
380
381 if (hit) {
382 data->uri_hits = g_list_prepend (data->uri_hits, g_file_get_uri (child));
383 }
384
385 data->n_processed_files++;
386 if (data->n_processed_files > BATCH_SIZE) {
387 send_batch (data);
388 }
389
390 if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
391 id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE);
392 visited = FALSE;
393 if (id) {
394 if (g_hash_table_lookup_extended (data->visited,
395 id, NULL, NULL)) {
396 visited = TRUE;
397 } else {
398 g_hash_table_insert (data->visited, g_strdup (id), NULL);
399 }
400 }
401
402 if (!visited) {
403 g_queue_push_tail (data->directories, g_object_ref (child));
404 }
405 }
406
407 g_object_unref (child);
408 next:
409 g_object_unref (info);
410 }
411
412 g_object_unref (enumerator);
413 }
414
415
416 static gpointer
search_thread_func(gpointer user_data)417 search_thread_func (gpointer user_data)
418 {
419 SearchThreadData *data;
420 GFile *dir;
421 GFileInfo *info;
422 const char *id;
423
424 data = user_data;
425
426 /* Insert id for toplevel directory into visited */
427 dir = g_queue_peek_head (data->directories);
428 info = g_file_query_info (dir, G_FILE_ATTRIBUTE_ID_FILE, 0, data->cancellable, NULL);
429 if (info) {
430 id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE);
431 if (id) {
432 g_hash_table_insert (data->visited, g_strdup (id), NULL);
433 }
434 g_object_unref (info);
435 }
436
437 while (!g_cancellable_is_cancelled (data->cancellable) &&
438 (dir = g_queue_pop_head (data->directories)) != NULL) {
439 visit_directory (dir, data);
440 g_object_unref (dir);
441 }
442 send_batch (data);
443
444 g_idle_add (search_thread_done_idle, data);
445
446 return NULL;
447 }
448
449 static void
nemo_search_engine_simple_start(NemoSearchEngine * engine)450 nemo_search_engine_simple_start (NemoSearchEngine *engine)
451 {
452 NemoSearchEngineSimple *simple;
453 SearchThreadData *data;
454 GThread *thread;
455
456 simple = NEMO_SEARCH_ENGINE_SIMPLE (engine);
457
458 if (simple->details->active_search != NULL) {
459 return;
460 }
461
462 if (simple->details->query == NULL) {
463 return;
464 }
465
466 data = search_thread_data_new (simple, simple->details->query);
467
468 thread = g_thread_new ("nemo-search-simple", search_thread_func, data);
469 simple->details->active_search = data;
470
471 g_thread_unref (thread);
472 }
473
474 static void
nemo_search_engine_simple_stop(NemoSearchEngine * engine)475 nemo_search_engine_simple_stop (NemoSearchEngine *engine)
476 {
477 NemoSearchEngineSimple *simple;
478
479 simple = NEMO_SEARCH_ENGINE_SIMPLE (engine);
480
481 if (simple->details->active_search != NULL) {
482 g_cancellable_cancel (simple->details->active_search->cancellable);
483 simple->details->active_search = NULL;
484 }
485 }
486
487 static void
nemo_search_engine_simple_set_query(NemoSearchEngine * engine,NemoQuery * query)488 nemo_search_engine_simple_set_query (NemoSearchEngine *engine, NemoQuery *query)
489 {
490 NemoSearchEngineSimple *simple;
491
492 simple = NEMO_SEARCH_ENGINE_SIMPLE (engine);
493
494 if (query) {
495 g_object_ref (query);
496 }
497
498 if (simple->details->query) {
499 g_object_unref (simple->details->query);
500 }
501
502 simple->details->query = query;
503 }
504
505 static void
nemo_search_engine_simple_class_init(NemoSearchEngineSimpleClass * class)506 nemo_search_engine_simple_class_init (NemoSearchEngineSimpleClass *class)
507 {
508 GObjectClass *gobject_class;
509 NemoSearchEngineClass *engine_class;
510
511 gobject_class = G_OBJECT_CLASS (class);
512 gobject_class->finalize = finalize;
513
514 engine_class = NEMO_SEARCH_ENGINE_CLASS (class);
515 engine_class->set_query = nemo_search_engine_simple_set_query;
516 engine_class->start = nemo_search_engine_simple_start;
517 engine_class->stop = nemo_search_engine_simple_stop;
518
519 g_type_class_add_private (class, sizeof (NemoSearchEngineSimpleDetails));
520 }
521
522 static void
nemo_search_engine_simple_init(NemoSearchEngineSimple * engine)523 nemo_search_engine_simple_init (NemoSearchEngineSimple *engine)
524 {
525 engine->details = G_TYPE_INSTANCE_GET_PRIVATE (engine, NEMO_TYPE_SEARCH_ENGINE_SIMPLE,
526 NemoSearchEngineSimpleDetails);
527 }
528
529 NemoSearchEngine *
nemo_search_engine_simple_new(void)530 nemo_search_engine_simple_new (void)
531 {
532 NemoSearchEngine *engine;
533
534 engine = g_object_new (NEMO_TYPE_SEARCH_ENGINE_SIMPLE, NULL);
535
536 return engine;
537 }
538