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