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