1 /*
2 * Copyright (C) 2005 Novell, Inc.
3 *
4 * Nautilus is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * Nautilus 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 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public
15 * License along with this program; see the file COPYING. If not,
16 * see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Anders Carlsson <andersca@imendio.com>
19 *
20 */
21
22 #include "nautilus-query.h"
23
24 #include <eel/eel-glib-extensions.h>
25 #include <glib/gi18n.h>
26
27 #include "nautilus-enum-types.h"
28 #include "nautilus-file-utilities.h"
29 #include "nautilus-global-preferences.h"
30
31 #define RANK_SCALE_FACTOR 100
32 #define MIN_RANK 10.0
33 #define MAX_RANK 50.0
34
35 struct _NautilusQuery
36 {
37 GObject parent;
38
39 char *text;
40 GFile *location;
41 GPtrArray *mime_types;
42 gboolean show_hidden;
43 GPtrArray *date_range;
44 NautilusQueryRecursive recursive;
45 NautilusQuerySearchType search_type;
46 NautilusQuerySearchContent search_content;
47
48 gboolean searching;
49 char **prepared_words;
50 GMutex prepared_words_mutex;
51 };
52
53 static void nautilus_query_class_init (NautilusQueryClass *class);
54 static void nautilus_query_init (NautilusQuery *query);
55
56 G_DEFINE_TYPE (NautilusQuery, nautilus_query, G_TYPE_OBJECT);
57
58 enum
59 {
60 PROP_0,
61 PROP_DATE_RANGE,
62 PROP_LOCATION,
63 PROP_MIMETYPES,
64 PROP_RECURSIVE,
65 PROP_SEARCH_TYPE,
66 PROP_SEARCHING,
67 PROP_SHOW_HIDDEN,
68 PROP_TEXT,
69 LAST_PROP
70 };
71
72 static void
finalize(GObject * object)73 finalize (GObject *object)
74 {
75 NautilusQuery *query;
76
77 query = NAUTILUS_QUERY (object);
78
79 g_free (query->text);
80 g_strfreev (query->prepared_words);
81 g_clear_object (&query->location);
82 g_clear_pointer (&query->date_range, g_ptr_array_unref);
83 g_mutex_clear (&query->prepared_words_mutex);
84
85 G_OBJECT_CLASS (nautilus_query_parent_class)->finalize (object);
86 }
87
88 static void
nautilus_query_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)89 nautilus_query_get_property (GObject *object,
90 guint prop_id,
91 GValue *value,
92 GParamSpec *pspec)
93 {
94 NautilusQuery *self = NAUTILUS_QUERY (object);
95
96 switch (prop_id)
97 {
98 case PROP_DATE_RANGE:
99 {
100 g_value_set_pointer (value, self->date_range);
101 }
102 break;
103
104 case PROP_LOCATION:
105 {
106 g_value_set_object (value, self->location);
107 }
108 break;
109
110 case PROP_MIMETYPES:
111 {
112 g_value_set_pointer (value, self->mime_types);
113 }
114 break;
115
116 case PROP_RECURSIVE:
117 {
118 g_value_set_enum (value, self->recursive);
119 }
120 break;
121
122 case PROP_SEARCH_TYPE:
123 {
124 g_value_set_enum (value, self->search_type);
125 }
126 break;
127
128 case PROP_SEARCHING:
129 {
130 g_value_set_boolean (value, self->searching);
131 }
132 break;
133
134 case PROP_SHOW_HIDDEN:
135 {
136 g_value_set_boolean (value, self->show_hidden);
137 }
138 break;
139
140 case PROP_TEXT:
141 {
142 g_value_set_string (value, self->text);
143 }
144 break;
145
146 default:
147 {
148 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
149 }
150 }
151 }
152
153 static void
nautilus_query_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)154 nautilus_query_set_property (GObject *object,
155 guint prop_id,
156 const GValue *value,
157 GParamSpec *pspec)
158 {
159 NautilusQuery *self = NAUTILUS_QUERY (object);
160
161 switch (prop_id)
162 {
163 case PROP_DATE_RANGE:
164 {
165 nautilus_query_set_date_range (self, g_value_get_pointer (value));
166 }
167 break;
168
169 case PROP_LOCATION:
170 {
171 nautilus_query_set_location (self, g_value_get_object (value));
172 }
173 break;
174
175 case PROP_MIMETYPES:
176 {
177 nautilus_query_set_mime_types (self, g_value_get_pointer (value));
178 }
179 break;
180
181 case PROP_RECURSIVE:
182 {
183 nautilus_query_set_recursive (self, g_value_get_enum (value));
184 }
185 break;
186
187 case PROP_SEARCH_TYPE:
188 {
189 nautilus_query_set_search_type (self, g_value_get_enum (value));
190 }
191 break;
192
193 case PROP_SEARCHING:
194 {
195 nautilus_query_set_searching (self, g_value_get_boolean (value));
196 }
197 break;
198
199 case PROP_SHOW_HIDDEN:
200 {
201 nautilus_query_set_show_hidden_files (self, g_value_get_boolean (value));
202 }
203 break;
204
205 case PROP_TEXT:
206 {
207 nautilus_query_set_text (self, g_value_get_string (value));
208 }
209 break;
210
211 default:
212 {
213 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
214 }
215 }
216 }
217
218 static void
nautilus_query_class_init(NautilusQueryClass * class)219 nautilus_query_class_init (NautilusQueryClass *class)
220 {
221 GObjectClass *gobject_class;
222
223 gobject_class = G_OBJECT_CLASS (class);
224 gobject_class->finalize = finalize;
225 gobject_class->get_property = nautilus_query_get_property;
226 gobject_class->set_property = nautilus_query_set_property;
227
228 /**
229 * NautilusQuery::date-range:
230 *
231 * The date range of the query.
232 *
233 */
234 g_object_class_install_property (gobject_class,
235 PROP_DATE_RANGE,
236 g_param_spec_pointer ("date-range",
237 "Date range of the query",
238 "The range date of the query",
239 G_PARAM_READWRITE));
240
241 /**
242 * NautilusQuery::location:
243 *
244 * The location of the query.
245 *
246 */
247 g_object_class_install_property (gobject_class,
248 PROP_LOCATION,
249 g_param_spec_object ("location",
250 "Location of the query",
251 "The location of the query",
252 G_TYPE_FILE,
253 G_PARAM_READWRITE));
254
255 /**
256 * NautilusQuery::mimetypes: (type GPtrArray) (element-type gchar*)
257 *
258 * MIME types the query holds. An empty array means "Any type".
259 *
260 */
261 g_object_class_install_property (gobject_class,
262 PROP_MIMETYPES,
263 g_param_spec_pointer ("mimetypes",
264 "MIME types of the query",
265 "The MIME types of the query",
266 G_PARAM_READWRITE));
267
268 /**
269 * NautilusQuery::recursive:
270 *
271 * Whether the query is being performed on subdirectories or not.
272 *
273 */
274 g_object_class_install_property (gobject_class,
275 PROP_RECURSIVE,
276 g_param_spec_enum ("recursive",
277 "Whether the query is being performed on subdirectories",
278 "Whether the query is being performed on subdirectories or not",
279 NAUTILUS_TYPE_QUERY_RECURSIVE,
280 NAUTILUS_QUERY_RECURSIVE_ALWAYS,
281 G_PARAM_READWRITE));
282
283 /**
284 * NautilusQuery::search-type:
285 *
286 * The search type of the query.
287 *
288 */
289 g_object_class_install_property (gobject_class,
290 PROP_SEARCH_TYPE,
291 g_param_spec_enum ("search-type",
292 "Type of the query",
293 "The type of the query",
294 NAUTILUS_TYPE_QUERY_SEARCH_TYPE,
295 NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED,
296 G_PARAM_READWRITE));
297
298 /**
299 * NautilusQuery::searching:
300 *
301 * Whether the query is being performed or not.
302 *
303 */
304 g_object_class_install_property (gobject_class,
305 PROP_SEARCHING,
306 g_param_spec_boolean ("searching",
307 "Whether the query is being performed",
308 "Whether the query is being performed or not",
309 FALSE,
310 G_PARAM_READWRITE));
311
312 /**
313 * NautilusQuery::show-hidden:
314 *
315 * Whether the search should include hidden files.
316 *
317 */
318 g_object_class_install_property (gobject_class,
319 PROP_SHOW_HIDDEN,
320 g_param_spec_boolean ("show-hidden",
321 "Show hidden files",
322 "Whether the search should show hidden files",
323 FALSE,
324 G_PARAM_READWRITE));
325
326 /**
327 * NautilusQuery::text:
328 *
329 * The search string.
330 *
331 */
332 g_object_class_install_property (gobject_class,
333 PROP_TEXT,
334 g_param_spec_string ("text",
335 "Text of the search",
336 "The text string of the search",
337 NULL,
338 G_PARAM_READWRITE));
339 }
340
341 static void
nautilus_query_init(NautilusQuery * query)342 nautilus_query_init (NautilusQuery *query)
343 {
344 query->location = g_file_new_for_path (g_get_home_dir ());
345 query->mime_types = g_ptr_array_new ();
346 query->show_hidden = TRUE;
347 query->search_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type");
348 query->search_content = NAUTILUS_QUERY_SEARCH_CONTENT_SIMPLE;
349 g_mutex_init (&query->prepared_words_mutex);
350 }
351
352 static gchar *
prepare_string_for_compare(const gchar * string)353 prepare_string_for_compare (const gchar *string)
354 {
355 gchar *normalized, *res;
356
357 normalized = g_utf8_normalize (string, -1, G_NORMALIZE_NFD);
358 res = g_utf8_strdown (normalized, -1);
359 g_free (normalized);
360
361 return res;
362 }
363
364 gdouble
nautilus_query_matches_string(NautilusQuery * query,const gchar * string)365 nautilus_query_matches_string (NautilusQuery *query,
366 const gchar *string)
367 {
368 gchar *prepared_string, *ptr;
369 gboolean found;
370 gdouble retval;
371 gint idx, nonexact_malus;
372
373 if (!query->text)
374 {
375 return -1;
376 }
377
378 g_mutex_lock (&query->prepared_words_mutex);
379 if (!query->prepared_words)
380 {
381 prepared_string = prepare_string_for_compare (query->text);
382 query->prepared_words = g_strsplit (prepared_string, " ", -1);
383 g_free (prepared_string);
384 }
385
386 prepared_string = prepare_string_for_compare (string);
387 found = TRUE;
388 ptr = NULL;
389 nonexact_malus = 0;
390
391 for (idx = 0; query->prepared_words[idx] != NULL; idx++)
392 {
393 if ((ptr = strstr (prepared_string, query->prepared_words[idx])) == NULL)
394 {
395 found = FALSE;
396 break;
397 }
398
399 nonexact_malus += strlen (ptr) - strlen (query->prepared_words[idx]);
400 }
401 g_mutex_unlock (&query->prepared_words_mutex);
402
403 if (!found)
404 {
405 g_free (prepared_string);
406 return -1;
407 }
408
409 /* The rank value depends on the numbers of letters before and after the match.
410 * To make the prefix matches prefered over sufix ones, the number of letters
411 * after the match is divided by a factor, so that it decreases the rank by a
412 * smaller amount.
413 */
414 retval = MAX (MIN_RANK, MAX_RANK - (gdouble) (ptr - prepared_string) - (gdouble) nonexact_malus / RANK_SCALE_FACTOR);
415 g_free (prepared_string);
416
417 return retval;
418 }
419
420 NautilusQuery *
nautilus_query_new(void)421 nautilus_query_new (void)
422 {
423 return g_object_new (NAUTILUS_TYPE_QUERY, NULL);
424 }
425
426
427 char *
nautilus_query_get_text(NautilusQuery * query)428 nautilus_query_get_text (NautilusQuery *query)
429 {
430 g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL);
431
432 return g_strdup (query->text);
433 }
434
435 void
nautilus_query_set_text(NautilusQuery * query,const char * text)436 nautilus_query_set_text (NautilusQuery *query,
437 const char *text)
438 {
439 g_return_if_fail (NAUTILUS_IS_QUERY (query));
440
441 g_free (query->text);
442 query->text = g_strstrip (g_strdup (text));
443
444 g_mutex_lock (&query->prepared_words_mutex);
445 g_strfreev (query->prepared_words);
446 query->prepared_words = NULL;
447 g_mutex_unlock (&query->prepared_words_mutex);
448
449 g_object_notify (G_OBJECT (query), "text");
450 }
451
452 GFile *
nautilus_query_get_location(NautilusQuery * query)453 nautilus_query_get_location (NautilusQuery *query)
454 {
455 g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL);
456
457 return g_object_ref (query->location);
458 }
459
460 void
nautilus_query_set_location(NautilusQuery * query,GFile * location)461 nautilus_query_set_location (NautilusQuery *query,
462 GFile *location)
463 {
464 g_return_if_fail (NAUTILUS_IS_QUERY (query));
465
466 if (g_set_object (&query->location, location))
467 {
468 g_object_notify (G_OBJECT (query), "location");
469 }
470 }
471
472 /**
473 * nautilus_query_get_mime_type:
474 * @query: A #NautilusQuery
475 *
476 * Retrieves the current MIME Types filter from @query. Its content must not be
477 * modified. It can be read by multiple threads.
478 *
479 * Returns: (transfer container) A #GPtrArray reference with MIME type name strings.
480 */
481 GPtrArray *
nautilus_query_get_mime_types(NautilusQuery * query)482 nautilus_query_get_mime_types (NautilusQuery *query)
483 {
484 g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL);
485
486 return g_ptr_array_ref (query->mime_types);
487 }
488
489 /**
490 * nautilus_query_set_mime_types:
491 * @query: A #NautilusQuery
492 * @mime_types: (transfer none): A #GPtrArray of MIME type strings
493 *
494 * Set a new MIME types filter for @query. Once set, the filter must not be
495 * modified, and it can only be replaced by setting another filter.
496 *
497 * Search engines that are already running for a previous filter will ignore the
498 * new filter. So, the caller must ensure that the search will be reloaded
499 * afterwards.
500 */
501 void
nautilus_query_set_mime_types(NautilusQuery * query,GPtrArray * mime_types)502 nautilus_query_set_mime_types (NautilusQuery *query,
503 GPtrArray *mime_types)
504 {
505 g_return_if_fail (NAUTILUS_IS_QUERY (query));
506 g_return_if_fail (mime_types != NULL);
507
508 g_clear_pointer (&query->mime_types, g_ptr_array_unref);
509 query->mime_types = g_ptr_array_ref (mime_types);
510
511 g_object_notify (G_OBJECT (query), "mimetypes");
512 }
513
514 gboolean
nautilus_query_get_show_hidden_files(NautilusQuery * query)515 nautilus_query_get_show_hidden_files (NautilusQuery *query)
516 {
517 g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE);
518
519 return query->show_hidden;
520 }
521
522 void
nautilus_query_set_show_hidden_files(NautilusQuery * query,gboolean show_hidden)523 nautilus_query_set_show_hidden_files (NautilusQuery *query,
524 gboolean show_hidden)
525 {
526 g_return_if_fail (NAUTILUS_IS_QUERY (query));
527
528 if (query->show_hidden != show_hidden)
529 {
530 query->show_hidden = show_hidden;
531 g_object_notify (G_OBJECT (query), "show-hidden");
532 }
533 }
534
535 char *
nautilus_query_to_readable_string(NautilusQuery * query)536 nautilus_query_to_readable_string (NautilusQuery *query)
537 {
538 if (!query || !query->text || query->text[0] == '\0')
539 {
540 return g_strdup (_("Search"));
541 }
542
543 return g_strdup_printf (_("Search for “%s”"), query->text);
544 }
545
546 NautilusQuerySearchContent
nautilus_query_get_search_content(NautilusQuery * query)547 nautilus_query_get_search_content (NautilusQuery *query)
548 {
549 g_return_val_if_fail (NAUTILUS_IS_QUERY (query), -1);
550
551 return query->search_content;
552 }
553
554 void
nautilus_query_set_search_content(NautilusQuery * query,NautilusQuerySearchContent content)555 nautilus_query_set_search_content (NautilusQuery *query,
556 NautilusQuerySearchContent content)
557 {
558 g_return_if_fail (NAUTILUS_IS_QUERY (query));
559
560 if (query->search_content != content)
561 {
562 query->search_content = content;
563 g_object_notify (G_OBJECT (query), "search-type");
564 }
565 }
566
567 NautilusQuerySearchType
nautilus_query_get_search_type(NautilusQuery * query)568 nautilus_query_get_search_type (NautilusQuery *query)
569 {
570 g_return_val_if_fail (NAUTILUS_IS_QUERY (query), -1);
571
572 return query->search_type;
573 }
574
575 void
nautilus_query_set_search_type(NautilusQuery * query,NautilusQuerySearchType type)576 nautilus_query_set_search_type (NautilusQuery *query,
577 NautilusQuerySearchType type)
578 {
579 g_return_if_fail (NAUTILUS_IS_QUERY (query));
580
581 if (query->search_type != type)
582 {
583 query->search_type = type;
584 g_object_notify (G_OBJECT (query), "search-type");
585 }
586 }
587
588 /**
589 * nautilus_query_get_date_range:
590 * @query: a #NautilusQuery
591 *
592 * Retrieves the #GptrArray composed of #GDateTime representing the date range.
593 * This function is thread safe.
594 *
595 * Returns: (transfer full): the #GptrArray composed of #GDateTime representing the date range.
596 */
597 GPtrArray *
nautilus_query_get_date_range(NautilusQuery * query)598 nautilus_query_get_date_range (NautilusQuery *query)
599 {
600 static GMutex mutex;
601
602 g_return_val_if_fail (NAUTILUS_IS_QUERY (query), NULL);
603
604 g_mutex_lock (&mutex);
605 if (query->date_range)
606 {
607 g_ptr_array_ref (query->date_range);
608 }
609 g_mutex_unlock (&mutex);
610
611 return query->date_range;
612 }
613
614 void
nautilus_query_set_date_range(NautilusQuery * query,GPtrArray * date_range)615 nautilus_query_set_date_range (NautilusQuery *query,
616 GPtrArray *date_range)
617 {
618 g_return_if_fail (NAUTILUS_IS_QUERY (query));
619
620 g_clear_pointer (&query->date_range, g_ptr_array_unref);
621 if (date_range)
622 {
623 query->date_range = g_ptr_array_ref (date_range);
624 }
625
626 g_object_notify (G_OBJECT (query), "date-range");
627 }
628
629 gboolean
nautilus_query_get_searching(NautilusQuery * query)630 nautilus_query_get_searching (NautilusQuery *query)
631 {
632 g_return_val_if_fail (NAUTILUS_IS_QUERY (query), FALSE);
633
634 return query->searching;
635 }
636
637 void
nautilus_query_set_searching(NautilusQuery * query,gboolean searching)638 nautilus_query_set_searching (NautilusQuery *query,
639 gboolean searching)
640 {
641 g_return_if_fail (NAUTILUS_IS_QUERY (query));
642
643 searching = !!searching;
644
645 if (query->searching != searching)
646 {
647 query->searching = searching;
648
649 g_object_notify (G_OBJECT (query), "searching");
650 }
651 }
652
653 NautilusQueryRecursive
nautilus_query_get_recursive(NautilusQuery * query)654 nautilus_query_get_recursive (NautilusQuery *query)
655 {
656 g_return_val_if_fail (NAUTILUS_IS_QUERY (query),
657 NAUTILUS_QUERY_RECURSIVE_ALWAYS);
658
659 return query->recursive;
660 }
661
662 void
nautilus_query_set_recursive(NautilusQuery * query,NautilusQueryRecursive recursive)663 nautilus_query_set_recursive (NautilusQuery *query,
664 NautilusQueryRecursive recursive)
665 {
666 g_return_if_fail (NAUTILUS_IS_QUERY (query));
667
668 if (query->recursive != recursive)
669 {
670 query->recursive = recursive;
671
672 g_object_notify (G_OBJECT (query), "recursive");
673 }
674 }
675
676 gboolean
nautilus_query_is_empty(NautilusQuery * query)677 nautilus_query_is_empty (NautilusQuery *query)
678 {
679 if (!query)
680 {
681 return TRUE;
682 }
683
684 if (!query->date_range &&
685 (!query->text || (query->text && query->text[0] == '\0')) &&
686 query->mime_types->len == 0)
687 {
688 return TRUE;
689 }
690
691 return FALSE;
692 }
693