1 /*
2  *  Copyright (C) 2005 Novell, Inc
3  *
4  *  This program 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  *  This program 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; if not, see <http://www.gnu.org/licenses/>.
16  *
17  *  Author: Anders Carlsson <andersca@imendio.com>
18  */
19 
20 #include "nautilus-search-directory.h"
21 
22 #include <eel/eel-glib-extensions.h>
23 #include <gio/gio.h>
24 #include <gtk/gtk.h>
25 #include <string.h>
26 #include <sys/time.h>
27 
28 #include "nautilus-directory-private.h"
29 #include "nautilus-file-private.h"
30 #include "nautilus-file-utilities.h"
31 #include "nautilus-file.h"
32 #include "nautilus-query.h"
33 #include "nautilus-search-directory-file.h"
34 #include "nautilus-search-engine-model.h"
35 #include "nautilus-search-engine.h"
36 #include "nautilus-search-provider.h"
37 
38 struct _NautilusSearchDirectory
39 {
40     NautilusDirectory parent_instance;
41 
42     NautilusQuery *query;
43 
44     NautilusSearchEngine *engine;
45 
46     gboolean search_running;
47     /* When the search directory is stopped or cancelled, we migth wait
48      * until all data and signals from previous search are stopped and removed
49      * from the search engine. While this situation happens we don't want to connect
50      * clients to our signals, and we will wait until the search data and signals
51      * are valid and ready.
52      * The worst thing that can happens if we don't do this is that new clients
53      * migth get the information of old searchs if they are waiting_for_file_list.
54      * But that shouldn't be a big deal since old clients have the old information.
55      * But anyway it's currently unused for this case since the only client is
56      * nautilus-view and is not waiting_for_file_list :) .
57      *
58      * The other use case is for letting clients know if information of the directory
59      * is outdated or not valid. This might happens for automatic
60      * scheduled timeouts. */
61     gboolean search_ready_and_valid;
62 
63     GList *files;
64     GHashTable *files_hash;
65 
66     GList *monitor_list;
67     GList *callback_list;
68     GList *pending_callback_list;
69 
70     GBinding *binding;
71 
72     NautilusDirectory *base_model;
73 };
74 
75 typedef struct
76 {
77     gboolean monitor_hidden_files;
78     NautilusFileAttributes monitor_attributes;
79 
80     gconstpointer client;
81 } SearchMonitor;
82 
83 typedef struct
84 {
85     NautilusSearchDirectory *search_directory;
86 
87     NautilusDirectoryCallback callback;
88     gpointer callback_data;
89 
90     NautilusFileAttributes wait_for_attributes;
91     gboolean wait_for_file_list;
92     GList *file_list;
93     GHashTable *non_ready_hash;
94 } SearchCallback;
95 
96 enum
97 {
98     PROP_0,
99     PROP_BASE_MODEL,
100     PROP_QUERY,
101     NUM_PROPERTIES
102 };
103 
104 G_DEFINE_TYPE_WITH_CODE (NautilusSearchDirectory, nautilus_search_directory, NAUTILUS_TYPE_DIRECTORY,
105                          nautilus_ensure_extension_points ();
106                          /* It looks like you’re implementing an extension point.
107                           * Did you modify nautilus_ensure_extension_builtins() accordingly?
108                           *
109                           * • Yes
110                           * • Doing it right now
111                           */
112                          g_io_extension_point_implement (NAUTILUS_DIRECTORY_PROVIDER_EXTENSION_POINT_NAME,
113                                                          g_define_type_id,
114                                                          NAUTILUS_SEARCH_DIRECTORY_PROVIDER_NAME,
115                                                          0));
116 
117 static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
118 
119 static void search_engine_hits_added (NautilusSearchEngine    *engine,
120                                       GList                   *hits,
121                                       NautilusSearchDirectory *self);
122 static void search_engine_error (NautilusSearchEngine    *engine,
123                                  const char              *error,
124                                  NautilusSearchDirectory *self);
125 static void search_callback_file_ready_callback (NautilusFile *file,
126                                                  gpointer      data);
127 static void file_changed (NautilusFile            *file,
128                           NautilusSearchDirectory *self);
129 
130 static void
reset_file_list(NautilusSearchDirectory * self)131 reset_file_list (NautilusSearchDirectory *self)
132 {
133     GList *list, *monitor_list;
134     NautilusFile *file;
135     SearchMonitor *monitor;
136 
137     /* Remove file connections */
138     for (list = self->files; list != NULL; list = list->next)
139     {
140         file = list->data;
141 
142         /* Disconnect change handler */
143         g_signal_handlers_disconnect_by_func (file, file_changed, self);
144 
145         /* Remove monitors */
146         for (monitor_list = self->monitor_list; monitor_list;
147              monitor_list = monitor_list->next)
148         {
149             monitor = monitor_list->data;
150             nautilus_file_monitor_remove (file, monitor);
151         }
152     }
153 
154     nautilus_file_list_free (self->files);
155     self->files = NULL;
156 
157     g_hash_table_remove_all (self->files_hash);
158 }
159 
160 static void
set_hidden_files(NautilusSearchDirectory * self)161 set_hidden_files (NautilusSearchDirectory *self)
162 {
163     GList *l;
164     SearchMonitor *monitor;
165     gboolean monitor_hidden = FALSE;
166 
167     for (l = self->monitor_list; l != NULL; l = l->next)
168     {
169         monitor = l->data;
170         monitor_hidden |= monitor->monitor_hidden_files;
171 
172         if (monitor_hidden)
173         {
174             break;
175         }
176     }
177 
178     nautilus_query_set_show_hidden_files (self->query, monitor_hidden);
179 }
180 
181 static void
start_search(NautilusSearchDirectory * self)182 start_search (NautilusSearchDirectory *self)
183 {
184     NautilusSearchEngineModel *model_provider;
185 
186     if (!self->query)
187     {
188         return;
189     }
190 
191     if (self->search_running)
192     {
193         return;
194     }
195 
196     if (!self->monitor_list && !self->pending_callback_list)
197     {
198         return;
199     }
200 
201     /* We need to start the search engine */
202     self->search_running = TRUE;
203     self->search_ready_and_valid = FALSE;
204 
205     set_hidden_files (self);
206     nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (self->engine),
207                                         self->query);
208 
209     model_provider = nautilus_search_engine_get_model_provider (self->engine);
210     nautilus_search_engine_model_set_model (model_provider, self->base_model);
211 
212     reset_file_list (self);
213 
214     nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (self->engine));
215 }
216 
217 static void
stop_search(NautilusSearchDirectory * self)218 stop_search (NautilusSearchDirectory *self)
219 {
220     if (!self->search_running)
221     {
222         return;
223     }
224 
225     self->search_running = FALSE;
226     nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (self->engine));
227 
228     reset_file_list (self);
229 }
230 
231 static void
file_changed(NautilusFile * file,NautilusSearchDirectory * self)232 file_changed (NautilusFile            *file,
233               NautilusSearchDirectory *self)
234 {
235     GList list;
236 
237     list.data = file;
238     list.next = NULL;
239 
240     nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (self), &list);
241 }
242 
243 static void
search_monitor_add(NautilusDirectory * directory,gconstpointer client,gboolean monitor_hidden_files,NautilusFileAttributes file_attributes,NautilusDirectoryCallback callback,gpointer callback_data)244 search_monitor_add (NautilusDirectory         *directory,
245                     gconstpointer              client,
246                     gboolean                   monitor_hidden_files,
247                     NautilusFileAttributes     file_attributes,
248                     NautilusDirectoryCallback  callback,
249                     gpointer                   callback_data)
250 {
251     GList *list;
252     SearchMonitor *monitor;
253     NautilusSearchDirectory *self;
254     NautilusFile *file;
255 
256     self = NAUTILUS_SEARCH_DIRECTORY (directory);
257 
258     monitor = g_new0 (SearchMonitor, 1);
259     monitor->monitor_hidden_files = monitor_hidden_files;
260     monitor->monitor_attributes = file_attributes;
261     monitor->client = client;
262 
263     self->monitor_list = g_list_prepend (self->monitor_list, monitor);
264 
265     if (callback != NULL)
266     {
267         (*callback)(directory, self->files, callback_data);
268     }
269 
270     for (list = self->files; list != NULL; list = list->next)
271     {
272         file = list->data;
273 
274         /* Add monitors */
275         nautilus_file_monitor_add (file, monitor, file_attributes);
276     }
277 
278     start_search (self);
279 }
280 
281 static void
search_monitor_remove_file_monitors(SearchMonitor * monitor,NautilusSearchDirectory * self)282 search_monitor_remove_file_monitors (SearchMonitor           *monitor,
283                                      NautilusSearchDirectory *self)
284 {
285     GList *list;
286     NautilusFile *file;
287 
288     for (list = self->files; list != NULL; list = list->next)
289     {
290         file = list->data;
291 
292         nautilus_file_monitor_remove (file, monitor);
293     }
294 }
295 
296 static void
search_monitor_destroy(SearchMonitor * monitor,NautilusSearchDirectory * self)297 search_monitor_destroy (SearchMonitor           *monitor,
298                         NautilusSearchDirectory *self)
299 {
300     search_monitor_remove_file_monitors (monitor, self);
301 
302     g_free (monitor);
303 }
304 
305 static void
search_monitor_remove(NautilusDirectory * directory,gconstpointer client)306 search_monitor_remove (NautilusDirectory *directory,
307                        gconstpointer      client)
308 {
309     NautilusSearchDirectory *self;
310     SearchMonitor *monitor;
311     GList *list;
312 
313     self = NAUTILUS_SEARCH_DIRECTORY (directory);
314 
315     for (list = self->monitor_list; list != NULL; list = list->next)
316     {
317         monitor = list->data;
318 
319         if (monitor->client == client)
320         {
321             self->monitor_list = g_list_delete_link (self->monitor_list, list);
322 
323             search_monitor_destroy (monitor, self);
324 
325             break;
326         }
327     }
328 
329     if (!self->monitor_list)
330     {
331         stop_search (self);
332     }
333 }
334 
335 static void
cancel_call_when_ready(gpointer key,gpointer value,gpointer user_data)336 cancel_call_when_ready (gpointer key,
337                         gpointer value,
338                         gpointer user_data)
339 {
340     SearchCallback *search_callback;
341     NautilusFile *file;
342 
343     file = key;
344     search_callback = user_data;
345 
346     nautilus_file_cancel_call_when_ready (file, search_callback_file_ready_callback,
347                                           search_callback);
348 }
349 
350 static void
search_callback_destroy(SearchCallback * search_callback)351 search_callback_destroy (SearchCallback *search_callback)
352 {
353     if (search_callback->non_ready_hash)
354     {
355         g_hash_table_foreach (search_callback->non_ready_hash, cancel_call_when_ready, search_callback);
356         g_hash_table_destroy (search_callback->non_ready_hash);
357     }
358 
359     nautilus_file_list_free (search_callback->file_list);
360 
361     g_free (search_callback);
362 }
363 
364 static void
search_callback_invoke_and_destroy(SearchCallback * search_callback)365 search_callback_invoke_and_destroy (SearchCallback *search_callback)
366 {
367     search_callback->callback (NAUTILUS_DIRECTORY (search_callback->search_directory),
368                                search_callback->file_list,
369                                search_callback->callback_data);
370 
371     search_callback->search_directory->callback_list =
372         g_list_remove (search_callback->search_directory->callback_list, search_callback);
373 
374     search_callback_destroy (search_callback);
375 }
376 
377 static void
search_callback_file_ready_callback(NautilusFile * file,gpointer data)378 search_callback_file_ready_callback (NautilusFile *file,
379                                      gpointer      data)
380 {
381     SearchCallback *search_callback = data;
382 
383     g_hash_table_remove (search_callback->non_ready_hash, file);
384 
385     if (g_hash_table_size (search_callback->non_ready_hash) == 0)
386     {
387         search_callback_invoke_and_destroy (search_callback);
388     }
389 }
390 
391 static void
search_callback_add_file_callbacks(SearchCallback * callback)392 search_callback_add_file_callbacks (SearchCallback *callback)
393 {
394     GList *file_list_copy, *list;
395     NautilusFile *file;
396 
397     file_list_copy = g_list_copy (callback->file_list);
398 
399     for (list = file_list_copy; list != NULL; list = list->next)
400     {
401         file = list->data;
402 
403         nautilus_file_call_when_ready (file,
404                                        callback->wait_for_attributes,
405                                        search_callback_file_ready_callback,
406                                        callback);
407     }
408     g_list_free (file_list_copy);
409 }
410 
411 static SearchCallback *
search_callback_find(NautilusSearchDirectory * self,NautilusDirectoryCallback callback,gpointer callback_data)412 search_callback_find (NautilusSearchDirectory   *self,
413                       NautilusDirectoryCallback  callback,
414                       gpointer                   callback_data)
415 {
416     SearchCallback *search_callback;
417     GList *list;
418 
419     for (list = self->callback_list; list != NULL; list = list->next)
420     {
421         search_callback = list->data;
422 
423         if (search_callback->callback == callback &&
424             search_callback->callback_data == callback_data)
425         {
426             return search_callback;
427         }
428     }
429 
430     return NULL;
431 }
432 
433 static SearchCallback *
search_callback_find_pending(NautilusSearchDirectory * self,NautilusDirectoryCallback callback,gpointer callback_data)434 search_callback_find_pending (NautilusSearchDirectory   *self,
435                               NautilusDirectoryCallback  callback,
436                               gpointer                   callback_data)
437 {
438     SearchCallback *search_callback;
439     GList *list;
440 
441     for (list = self->pending_callback_list; list != NULL; list = list->next)
442     {
443         search_callback = list->data;
444 
445         if (search_callback->callback == callback &&
446             search_callback->callback_data == callback_data)
447         {
448             return search_callback;
449         }
450     }
451 
452     return NULL;
453 }
454 
455 static GHashTable *
file_list_to_hash_table(GList * file_list)456 file_list_to_hash_table (GList *file_list)
457 {
458     GList *list;
459     GHashTable *table;
460 
461     if (!file_list)
462     {
463         return NULL;
464     }
465 
466     table = g_hash_table_new (NULL, NULL);
467 
468     for (list = file_list; list != NULL; list = list->next)
469     {
470         g_hash_table_insert (table, list->data, list->data);
471     }
472 
473     return table;
474 }
475 
476 static void
search_call_when_ready(NautilusDirectory * directory,NautilusFileAttributes file_attributes,gboolean wait_for_file_list,NautilusDirectoryCallback callback,gpointer callback_data)477 search_call_when_ready (NautilusDirectory         *directory,
478                         NautilusFileAttributes     file_attributes,
479                         gboolean                   wait_for_file_list,
480                         NautilusDirectoryCallback  callback,
481                         gpointer                   callback_data)
482 {
483     NautilusSearchDirectory *self;
484     SearchCallback *search_callback;
485 
486     self = NAUTILUS_SEARCH_DIRECTORY (directory);
487 
488     search_callback = search_callback_find (self, callback, callback_data);
489     if (search_callback == NULL)
490     {
491         search_callback = search_callback_find_pending (self, callback, callback_data);
492     }
493 
494     if (search_callback)
495     {
496         g_warning ("tried to add a new callback while an old one was pending");
497         return;
498     }
499 
500     search_callback = g_new0 (SearchCallback, 1);
501     search_callback->search_directory = self;
502     search_callback->callback = callback;
503     search_callback->callback_data = callback_data;
504     search_callback->wait_for_attributes = file_attributes;
505     search_callback->wait_for_file_list = wait_for_file_list;
506 
507     if (wait_for_file_list && !self->search_ready_and_valid)
508     {
509         /* Add it to the pending callback list, which will be
510          * processed when the directory has valid data from the new
511          * search and all data and signals from previous searchs is removed. */
512         self->pending_callback_list =
513             g_list_prepend (self->pending_callback_list, search_callback);
514 
515         /* We might need to start the search engine */
516         start_search (self);
517     }
518     else
519     {
520         search_callback->file_list = nautilus_file_list_copy (self->files);
521         search_callback->non_ready_hash = file_list_to_hash_table (self->files);
522 
523         if (!search_callback->non_ready_hash)
524         {
525             /* If there are no ready files, we invoke the callback
526              *  with an empty list.
527              */
528             search_callback_invoke_and_destroy (search_callback);
529         }
530         else
531         {
532             self->callback_list = g_list_prepend (self->callback_list, search_callback);
533             search_callback_add_file_callbacks (search_callback);
534         }
535     }
536 }
537 
538 static void
search_cancel_callback(NautilusDirectory * directory,NautilusDirectoryCallback callback,gpointer callback_data)539 search_cancel_callback (NautilusDirectory         *directory,
540                         NautilusDirectoryCallback  callback,
541                         gpointer                   callback_data)
542 {
543     NautilusSearchDirectory *self;
544     SearchCallback *search_callback;
545 
546     self = NAUTILUS_SEARCH_DIRECTORY (directory);
547     search_callback = search_callback_find (self, callback, callback_data);
548 
549     if (search_callback)
550     {
551         self->callback_list = g_list_remove (self->callback_list, search_callback);
552 
553         search_callback_destroy (search_callback);
554 
555         goto done;
556     }
557 
558     /* Check for a pending callback */
559     search_callback = search_callback_find_pending (self, callback, callback_data);
560 
561     if (search_callback)
562     {
563         self->pending_callback_list = g_list_remove (self->pending_callback_list, search_callback);
564 
565         search_callback_destroy (search_callback);
566     }
567 
568 done:
569     if (!self->callback_list && !self->pending_callback_list)
570     {
571         stop_search (self);
572     }
573 }
574 
575 static void
search_callback_add_pending_file_callbacks(SearchCallback * callback)576 search_callback_add_pending_file_callbacks (SearchCallback *callback)
577 {
578     callback->file_list = nautilus_file_list_copy (callback->search_directory->files);
579     callback->non_ready_hash = file_list_to_hash_table (callback->search_directory->files);
580 
581     search_callback_add_file_callbacks (callback);
582 }
583 
584 static void
search_directory_add_pending_files_callbacks(NautilusSearchDirectory * self)585 search_directory_add_pending_files_callbacks (NautilusSearchDirectory *self)
586 {
587     /* Add all file callbacks */
588     g_list_foreach (self->pending_callback_list,
589                     (GFunc) search_callback_add_pending_file_callbacks, NULL);
590     self->callback_list = g_list_concat (self->callback_list,
591                                          self->pending_callback_list);
592 
593     g_list_free (self->pending_callback_list);
594     self->pending_callback_list = NULL;
595 }
596 
597 static void
on_search_directory_search_ready_and_valid(NautilusSearchDirectory * self)598 on_search_directory_search_ready_and_valid (NautilusSearchDirectory *self)
599 {
600     search_directory_add_pending_files_callbacks (self);
601     self->search_ready_and_valid = TRUE;
602 }
603 
604 static void
search_engine_hits_added(NautilusSearchEngine * engine,GList * hits,NautilusSearchDirectory * self)605 search_engine_hits_added (NautilusSearchEngine    *engine,
606                           GList                   *hits,
607                           NautilusSearchDirectory *self)
608 {
609     GList *hit_list;
610     GList *file_list;
611     NautilusFile *file;
612     SearchMonitor *monitor;
613     GList *monitor_list;
614 
615     file_list = NULL;
616 
617     for (hit_list = hits; hit_list != NULL; hit_list = hit_list->next)
618     {
619         NautilusSearchHit *hit = hit_list->data;
620         const char *uri;
621 
622         uri = nautilus_search_hit_get_uri (hit);
623 
624         nautilus_search_hit_compute_scores (hit, self->query);
625 
626         file = nautilus_file_get_by_uri (uri);
627         nautilus_file_set_search_relevance (file, nautilus_search_hit_get_relevance (hit));
628         nautilus_file_set_search_fts_snippet (file, nautilus_search_hit_get_fts_snippet (hit));
629 
630         for (monitor_list = self->monitor_list; monitor_list; monitor_list = monitor_list->next)
631         {
632             monitor = monitor_list->data;
633 
634             /* Add monitors */
635             nautilus_file_monitor_add (file, monitor, monitor->monitor_attributes);
636         }
637 
638         g_signal_connect (file, "changed", G_CALLBACK (file_changed), self),
639 
640         file_list = g_list_prepend (file_list, file);
641         g_hash_table_add (self->files_hash, file);
642     }
643 
644     self->files = g_list_concat (self->files, file_list);
645 
646     nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (self), file_list);
647 
648     file = nautilus_directory_get_corresponding_file (NAUTILUS_DIRECTORY (self));
649     nautilus_file_emit_changed (file);
650     nautilus_file_unref (file);
651 
652     search_directory_add_pending_files_callbacks (self);
653 }
654 
655 static void
search_engine_error(NautilusSearchEngine * engine,const char * error_message,NautilusSearchDirectory * self)656 search_engine_error (NautilusSearchEngine    *engine,
657                      const char              *error_message,
658                      NautilusSearchDirectory *self)
659 {
660     GError *error;
661 
662     error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
663                                  error_message);
664     nautilus_directory_emit_load_error (NAUTILUS_DIRECTORY (self),
665                                         error);
666     g_error_free (error);
667 }
668 
669 static void
search_engine_finished(NautilusSearchEngine * engine,NautilusSearchProviderStatus status,NautilusSearchDirectory * self)670 search_engine_finished (NautilusSearchEngine         *engine,
671                         NautilusSearchProviderStatus  status,
672                         NautilusSearchDirectory      *self)
673 {
674     /* If the search engine is going to restart means it finished an old search
675      * that was stopped or cancelled.
676      * Don't emit the done loading signal in this case, since this means the search
677      * directory tried to start a new search before all the search providers were finished
678      * in the search engine.
679      * If we emit the done-loading signal in this situation the client will think
680      * that it finished the current search, not an old one like it's actually
681      * happening. */
682     if (status == NAUTILUS_SEARCH_PROVIDER_STATUS_NORMAL)
683     {
684         on_search_directory_search_ready_and_valid (self);
685         nautilus_directory_emit_done_loading (NAUTILUS_DIRECTORY (self));
686     }
687     else if (status == NAUTILUS_SEARCH_PROVIDER_STATUS_RESTARTING)
688     {
689         /* Remove file monitors of the files from an old search that just
690          * actually finished */
691         reset_file_list (self);
692     }
693 }
694 
695 static void
search_force_reload(NautilusDirectory * directory)696 search_force_reload (NautilusDirectory *directory)
697 {
698     NautilusSearchDirectory *self;
699     NautilusFile *file;
700 
701     self = NAUTILUS_SEARCH_DIRECTORY (directory);
702 
703     if (!self->query)
704     {
705         return;
706     }
707 
708     self->search_ready_and_valid = FALSE;
709 
710     /* Remove file monitors */
711     reset_file_list (self);
712     stop_search (self);
713 
714     file = nautilus_directory_get_corresponding_file (directory);
715     nautilus_file_invalidate_all_attributes (file);
716     nautilus_file_unref (file);
717 }
718 
719 static gboolean
search_are_all_files_seen(NautilusDirectory * directory)720 search_are_all_files_seen (NautilusDirectory *directory)
721 {
722     NautilusSearchDirectory *self;
723 
724     self = NAUTILUS_SEARCH_DIRECTORY (directory);
725 
726     return (!self->query ||
727             self->search_ready_and_valid);
728 }
729 
730 static gboolean
search_contains_file(NautilusDirectory * directory,NautilusFile * file)731 search_contains_file (NautilusDirectory *directory,
732                       NautilusFile      *file)
733 {
734     NautilusSearchDirectory *self;
735 
736     self = NAUTILUS_SEARCH_DIRECTORY (directory);
737     return (g_hash_table_lookup (self->files_hash, file) != NULL);
738 }
739 
740 static GList *
search_get_file_list(NautilusDirectory * directory)741 search_get_file_list (NautilusDirectory *directory)
742 {
743     NautilusSearchDirectory *self;
744 
745     self = NAUTILUS_SEARCH_DIRECTORY (directory);
746 
747     return nautilus_file_list_copy (self->files);
748 }
749 
750 
751 static gboolean
search_is_editable(NautilusDirectory * directory)752 search_is_editable (NautilusDirectory *directory)
753 {
754     return FALSE;
755 }
756 
757 static gboolean
real_handles_location(GFile * location)758 real_handles_location (GFile *location)
759 {
760     g_autofree gchar *uri = NULL;
761 
762     uri = g_file_get_uri (location);
763 
764     return eel_uri_is_search (uri);
765 }
766 
767 static void
search_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)768 search_set_property (GObject      *object,
769                      guint         property_id,
770                      const GValue *value,
771                      GParamSpec   *pspec)
772 {
773     NautilusSearchDirectory *self = NAUTILUS_SEARCH_DIRECTORY (object);
774 
775     switch (property_id)
776     {
777         case PROP_BASE_MODEL:
778         {
779             nautilus_search_directory_set_base_model (self, g_value_get_object (value));
780         }
781         break;
782 
783         case PROP_QUERY:
784         {
785             nautilus_search_directory_set_query (self, g_value_get_object (value));
786         }
787         break;
788 
789         default:
790         {
791             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
792         }
793         break;
794     }
795 }
796 
797 static void
search_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)798 search_get_property (GObject    *object,
799                      guint       property_id,
800                      GValue     *value,
801                      GParamSpec *pspec)
802 {
803     NautilusSearchDirectory *self = NAUTILUS_SEARCH_DIRECTORY (object);
804 
805     switch (property_id)
806     {
807         case PROP_BASE_MODEL:
808         {
809             g_value_set_object (value, nautilus_search_directory_get_base_model (self));
810         }
811         break;
812 
813         case PROP_QUERY:
814         {
815             g_value_take_object (value, nautilus_search_directory_get_query (self));
816         }
817         break;
818 
819         default:
820         {
821             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
822         }
823         break;
824     }
825 }
826 
827 static void
clear_base_model(NautilusSearchDirectory * self)828 clear_base_model (NautilusSearchDirectory *self)
829 {
830     if (self->base_model != NULL)
831     {
832         nautilus_directory_file_monitor_remove (self->base_model,
833                                                 &self->base_model);
834         g_clear_object (&self->base_model);
835     }
836 }
837 
838 static void
search_connect_engine(NautilusSearchDirectory * self)839 search_connect_engine (NautilusSearchDirectory *self)
840 {
841     g_signal_connect (self->engine, "hits-added",
842                       G_CALLBACK (search_engine_hits_added),
843                       self);
844     g_signal_connect (self->engine, "error",
845                       G_CALLBACK (search_engine_error),
846                       self);
847     g_signal_connect (self->engine, "finished",
848                       G_CALLBACK (search_engine_finished),
849                       self);
850 }
851 
852 static void
search_disconnect_engine(NautilusSearchDirectory * self)853 search_disconnect_engine (NautilusSearchDirectory *self)
854 {
855     g_signal_handlers_disconnect_by_func (self->engine,
856                                           search_engine_hits_added,
857                                           self);
858     g_signal_handlers_disconnect_by_func (self->engine,
859                                           search_engine_error,
860                                           self);
861     g_signal_handlers_disconnect_by_func (self->engine,
862                                           search_engine_finished,
863                                           self);
864 }
865 
866 static void
search_dispose(GObject * object)867 search_dispose (GObject *object)
868 {
869     NautilusSearchDirectory *self;
870     GList *list;
871 
872     self = NAUTILUS_SEARCH_DIRECTORY (object);
873 
874     clear_base_model (self);
875 
876     /* Remove search monitors */
877     if (self->monitor_list)
878     {
879         for (list = self->monitor_list; list != NULL; list = list->next)
880         {
881             search_monitor_destroy ((SearchMonitor *) list->data, self);
882         }
883 
884         g_list_free (self->monitor_list);
885         self->monitor_list = NULL;
886     }
887 
888     reset_file_list (self);
889 
890     if (self->callback_list)
891     {
892         /* Remove callbacks */
893         g_list_foreach (self->callback_list,
894                         (GFunc) search_callback_destroy, NULL);
895         g_list_free (self->callback_list);
896         self->callback_list = NULL;
897     }
898 
899     if (self->pending_callback_list)
900     {
901         g_list_foreach (self->pending_callback_list,
902                         (GFunc) search_callback_destroy, NULL);
903         g_list_free (self->pending_callback_list);
904         self->pending_callback_list = NULL;
905     }
906 
907     g_clear_object (&self->query);
908     stop_search (self);
909     search_disconnect_engine (self);
910 
911     g_clear_object (&self->engine);
912 
913     G_OBJECT_CLASS (nautilus_search_directory_parent_class)->dispose (object);
914 }
915 
916 static void
search_finalize(GObject * object)917 search_finalize (GObject *object)
918 {
919     NautilusSearchDirectory *self;
920 
921     self = NAUTILUS_SEARCH_DIRECTORY (object);
922 
923     g_hash_table_destroy (self->files_hash);
924 
925     G_OBJECT_CLASS (nautilus_search_directory_parent_class)->finalize (object);
926 }
927 
928 static void
nautilus_search_directory_init(NautilusSearchDirectory * self)929 nautilus_search_directory_init (NautilusSearchDirectory *self)
930 {
931     self->query = NULL;
932     self->files_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
933 
934     self->engine = nautilus_search_engine_new ();
935     search_connect_engine (self);
936 }
937 
938 static void
nautilus_search_directory_class_init(NautilusSearchDirectoryClass * class)939 nautilus_search_directory_class_init (NautilusSearchDirectoryClass *class)
940 {
941     NautilusDirectoryClass *directory_class = NAUTILUS_DIRECTORY_CLASS (class);
942     GObjectClass *oclass = G_OBJECT_CLASS (class);
943 
944     oclass->dispose = search_dispose;
945     oclass->finalize = search_finalize;
946     oclass->get_property = search_get_property;
947     oclass->set_property = search_set_property;
948 
949     directory_class->are_all_files_seen = search_are_all_files_seen;
950     directory_class->contains_file = search_contains_file;
951     directory_class->force_reload = search_force_reload;
952     directory_class->call_when_ready = search_call_when_ready;
953     directory_class->cancel_callback = search_cancel_callback;
954 
955     directory_class->file_monitor_add = search_monitor_add;
956     directory_class->file_monitor_remove = search_monitor_remove;
957 
958     directory_class->get_file_list = search_get_file_list;
959     directory_class->is_editable = search_is_editable;
960     directory_class->handles_location = real_handles_location;
961 
962     properties[PROP_BASE_MODEL] =
963         g_param_spec_object ("base-model",
964                              "The base model",
965                              "The base directory model for this directory",
966                              NAUTILUS_TYPE_DIRECTORY,
967                              G_PARAM_READWRITE);
968     properties[PROP_QUERY] =
969         g_param_spec_object ("query",
970                              "The query",
971                              "The query for this search directory",
972                              NAUTILUS_TYPE_QUERY,
973                              G_PARAM_READWRITE);
974 
975     g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
976 }
977 
978 void
nautilus_search_directory_set_base_model(NautilusSearchDirectory * self,NautilusDirectory * base_model)979 nautilus_search_directory_set_base_model (NautilusSearchDirectory *self,
980                                           NautilusDirectory       *base_model)
981 {
982     if (self->base_model == base_model)
983     {
984         return;
985     }
986 
987     if (self->query != NULL)
988     {
989         GFile *query_location, *model_location;
990         gboolean is_equal;
991 
992         query_location = nautilus_query_get_location (self->query);
993         model_location = nautilus_directory_get_location (base_model);
994 
995         is_equal = g_file_equal (model_location, query_location);
996 
997         g_object_unref (model_location);
998         g_object_unref (query_location);
999 
1000         if (!is_equal)
1001         {
1002             return;
1003         }
1004     }
1005 
1006     clear_base_model (self);
1007     self->base_model = nautilus_directory_ref (base_model);
1008 
1009     if (self->base_model != NULL)
1010     {
1011         nautilus_directory_file_monitor_add (base_model, &self->base_model,
1012                                              TRUE, NAUTILUS_FILE_ATTRIBUTE_INFO,
1013                                              NULL, NULL);
1014     }
1015 
1016     g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BASE_MODEL]);
1017 }
1018 
1019 NautilusDirectory *
nautilus_search_directory_get_base_model(NautilusSearchDirectory * self)1020 nautilus_search_directory_get_base_model (NautilusSearchDirectory *self)
1021 {
1022     return self->base_model;
1023 }
1024 
1025 char *
nautilus_search_directory_generate_new_uri(void)1026 nautilus_search_directory_generate_new_uri (void)
1027 {
1028     static int counter = 0;
1029     char *uri;
1030 
1031     uri = g_strdup_printf (EEL_SEARCH_URI "//%d/", counter++);
1032 
1033     return uri;
1034 }
1035 
1036 void
nautilus_search_directory_set_query(NautilusSearchDirectory * self,NautilusQuery * query)1037 nautilus_search_directory_set_query (NautilusSearchDirectory *self,
1038                                      NautilusQuery           *query)
1039 {
1040     NautilusFile *file;
1041     NautilusQuery *old_query;
1042 
1043     old_query = self->query;
1044 
1045     if (self->query != query)
1046     {
1047         self->query = g_object_ref (query);
1048 
1049         g_clear_pointer (&self->binding, g_binding_unbind);
1050 
1051         if (query)
1052         {
1053             self->binding = g_object_bind_property (self->engine, "running",
1054                                                     query, "searching",
1055                                                     G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
1056         }
1057 
1058         g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_QUERY]);
1059 
1060         g_clear_object (&old_query);
1061     }
1062 
1063     file = nautilus_directory_get_existing_corresponding_file (NAUTILUS_DIRECTORY (self));
1064     if (file != NULL)
1065     {
1066         nautilus_search_directory_file_update_display_name (NAUTILUS_SEARCH_DIRECTORY_FILE (file));
1067     }
1068     nautilus_file_unref (file);
1069 }
1070 
1071 NautilusQuery *
nautilus_search_directory_get_query(NautilusSearchDirectory * self)1072 nautilus_search_directory_get_query (NautilusSearchDirectory *self)
1073 {
1074     if (self->query != NULL)
1075     {
1076         return g_object_ref (self->query);
1077     }
1078 
1079     return NULL;
1080 }
1081