diff options
Diffstat (limited to 'src/nautilus-shell-search-provider.c')
-rw-r--r-- | src/nautilus-shell-search-provider.c | 811 |
1 files changed, 811 insertions, 0 deletions
diff --git a/src/nautilus-shell-search-provider.c b/src/nautilus-shell-search-provider.c new file mode 100644 index 0000000..c0da929 --- /dev/null +++ b/src/nautilus-shell-search-provider.c @@ -0,0 +1,811 @@ +/* + * nautilus-shell-search-provider.c - Implementation of a GNOME Shell + * search provider + * + * Copyright (C) 2012 Red Hat, Inc. + * + * Nautilus is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Nautilus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Authors: Cosimo Cecchi <cosimoc@gnome.org> + * + */ + +#include <config.h> + +#include <gio/gio.h> +#include <string.h> +#include <glib/gi18n.h> +#include <gdk/gdk.h> + +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-search-engine.h" +#include "nautilus-search-provider.h" +#include "nautilus-ui-utilities.h" + +#include "nautilus-application.h" +#include "nautilus-bookmark-list.h" +#include "nautilus-shell-search-provider-generated.h" +#include "nautilus-shell-search-provider.h" + +typedef struct +{ + NautilusShellSearchProvider *self; + + NautilusSearchEngine *engine; + NautilusQuery *query; + + GHashTable *hits; + GDBusMethodInvocation *invocation; + + gint64 start_time; +} PendingSearch; + +struct _NautilusShellSearchProvider +{ + GObject parent; + + NautilusShellSearchProvider2 *skeleton; + + PendingSearch *current_search; + + GHashTable *metas_cache; +}; + +G_DEFINE_TYPE (NautilusShellSearchProvider, nautilus_shell_search_provider, G_TYPE_OBJECT) + +static gchar * +get_display_name (NautilusShellSearchProvider *self, + NautilusFile *file) +{ + GFile *location; + NautilusBookmark *bookmark; + NautilusBookmarkList *bookmarks; + + bookmarks = nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (g_application_get_default ())); + + location = nautilus_file_get_location (file); + bookmark = nautilus_bookmark_list_item_with_location (bookmarks, location, NULL); + g_object_unref (location); + + if (bookmark) + { + return g_strdup (nautilus_bookmark_get_name (bookmark)); + } + else + { + return nautilus_file_get_display_name (file); + } +} + +static GIcon * +get_gicon (NautilusShellSearchProvider *self, + NautilusFile *file) +{ + GFile *location; + NautilusBookmark *bookmark; + NautilusBookmarkList *bookmarks; + + bookmarks = nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (g_application_get_default ())); + + location = nautilus_file_get_location (file); + bookmark = nautilus_bookmark_list_item_with_location (bookmarks, location, NULL); + g_object_unref (location); + + if (bookmark) + { + return nautilus_bookmark_get_icon (bookmark); + } + else + { + return nautilus_file_get_gicon (file, 0); + } +} + +static void +pending_search_free (PendingSearch *search) +{ + g_hash_table_destroy (search->hits); + g_clear_object (&search->query); + g_clear_object (&search->engine); + g_clear_object (&search->invocation); + + g_slice_free (PendingSearch, search); +} + +static void +pending_search_finish (PendingSearch *search, + GDBusMethodInvocation *invocation, + GVariant *result) +{ + NautilusShellSearchProvider *self = search->self; + + g_dbus_method_invocation_return_value (invocation, result); + + if (search == self->current_search) + { + self->current_search = NULL; + } + + g_application_release (g_application_get_default ()); + pending_search_free (search); +} + +static void +cancel_current_search (NautilusShellSearchProvider *self) +{ + if (self->current_search != NULL) + { + nautilus_search_provider_stop (NAUTILUS_SEARCH_PROVIDER (self->current_search->engine)); + } +} + +static void +search_hits_added_cb (NautilusSearchEngine *engine, + GList *hits, + gpointer user_data) +{ + PendingSearch *search = user_data; + GList *l; + NautilusSearchHit *hit; + const gchar *hit_uri; + + g_debug ("*** Search engine hits added"); + + for (l = hits; l != NULL; l = l->next) + { + hit = l->data; + nautilus_search_hit_compute_scores (hit, search->query); + hit_uri = nautilus_search_hit_get_uri (hit); + g_debug (" %s", hit_uri); + + g_hash_table_replace (search->hits, g_strdup (hit_uri), g_object_ref (hit)); + } +} + +static gint +search_hit_compare_relevance (gconstpointer a, + gconstpointer b) +{ + NautilusSearchHit *hit_a, *hit_b; + gdouble relevance_a, relevance_b; + + hit_a = NAUTILUS_SEARCH_HIT ((gpointer) a); + hit_b = NAUTILUS_SEARCH_HIT ((gpointer) b); + + relevance_a = nautilus_search_hit_get_relevance (hit_a); + relevance_b = nautilus_search_hit_get_relevance (hit_b); + + if (relevance_a > relevance_b) + { + return -1; + } + else if (relevance_a == relevance_b) + { + return 0; + } + + return 1; +} + +static void +search_finished_cb (NautilusSearchEngine *engine, + NautilusSearchProviderStatus status, + gpointer user_data) +{ + PendingSearch *search = user_data; + GList *hits, *l; + NautilusSearchHit *hit; + GVariantBuilder builder; + gint64 current_time; + + current_time = g_get_monotonic_time (); + g_debug ("*** Search engine search finished - time elapsed %dms", + (gint) ((current_time - search->start_time) / 1000)); + + hits = g_hash_table_get_values (search->hits); + hits = g_list_sort (hits, search_hit_compare_relevance); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + + for (l = hits; l != NULL; l = l->next) + { + hit = l->data; + g_variant_builder_add (&builder, "s", nautilus_search_hit_get_uri (hit)); + } + + g_list_free (hits); + pending_search_finish (search, search->invocation, + g_variant_new ("(as)", &builder)); +} + +static void +search_error_cb (NautilusSearchEngine *engine, + const gchar *error_message, + gpointer user_data) +{ + NautilusShellSearchProvider *self = user_data; + PendingSearch *search = self->current_search; + + g_debug ("*** Search engine search error"); + pending_search_finish (search, search->invocation, + g_variant_new ("(as)", NULL)); +} + +typedef struct +{ + gchar *uri; + gchar *string_for_compare; +} SearchHitCandidate; + +static void +search_hit_candidate_free (SearchHitCandidate *candidate) +{ + g_free (candidate->uri); + g_free (candidate->string_for_compare); + + g_slice_free (SearchHitCandidate, candidate); +} + +static SearchHitCandidate * +search_hit_candidate_new (const gchar *uri, + const gchar *name) +{ + SearchHitCandidate *candidate = g_slice_new0 (SearchHitCandidate); + + candidate->uri = g_strdup (uri); + candidate->string_for_compare = g_strdup (name); + + return candidate; +} + +static void +search_add_volumes_and_bookmarks (PendingSearch *search) +{ + NautilusSearchHit *hit; + NautilusBookmark *bookmark; + const gchar *name; + gchar *string, *uri; + gdouble match; + GList *l, *m, *drives, *volumes, *mounts, *mounts_to_check, *candidates; + GDrive *drive; + GVolume *volume; + GMount *mount; + GFile *location; + SearchHitCandidate *candidate; + NautilusBookmarkList *bookmarks; + GList *all_bookmarks; + GVolumeMonitor *volume_monitor; + + bookmarks = nautilus_application_get_bookmarks (NAUTILUS_APPLICATION (g_application_get_default ())); + all_bookmarks = nautilus_bookmark_list_get_all (bookmarks); + volume_monitor = g_volume_monitor_get (); + candidates = NULL; + + /* first add bookmarks */ + for (l = all_bookmarks; l != NULL; l = l->next) + { + bookmark = NAUTILUS_BOOKMARK (l->data); + name = nautilus_bookmark_get_name (bookmark); + if (name == NULL) + { + continue; + } + + uri = nautilus_bookmark_get_uri (bookmark); + candidate = search_hit_candidate_new (uri, name); + candidates = g_list_prepend (candidates, candidate); + + g_free (uri); + } + + /* home dir */ + uri = nautilus_get_home_directory_uri (); + candidate = search_hit_candidate_new (uri, _("Home")); + candidates = g_list_prepend (candidates, candidate); + g_free (uri); + + /* trash */ + candidate = search_hit_candidate_new ("trash:///", _("Trash")); + candidates = g_list_prepend (candidates, candidate); + + /* now add mounts */ + mounts_to_check = NULL; + + /* first check all connected drives */ + drives = g_volume_monitor_get_connected_drives (volume_monitor); + for (l = drives; l != NULL; l = l->next) + { + drive = l->data; + volumes = g_drive_get_volumes (drive); + + for (m = volumes; m != NULL; m = m->next) + { + volume = m->data; + mount = g_volume_get_mount (volume); + if (mount != NULL) + { + mounts_to_check = g_list_prepend (mounts_to_check, mount); + } + } + + g_list_free_full (volumes, g_object_unref); + } + g_list_free_full (drives, g_object_unref); + + /* then volumes that don't have a drive */ + volumes = g_volume_monitor_get_volumes (volume_monitor); + for (l = volumes; l != NULL; l = l->next) + { + volume = l->data; + drive = g_volume_get_drive (volume); + + if (drive == NULL) + { + mount = g_volume_get_mount (volume); + if (mount != NULL) + { + mounts_to_check = g_list_prepend (mounts_to_check, mount); + } + } + g_clear_object (&drive); + } + g_list_free_full (volumes, g_object_unref); + + /* then mounts that have no volume */ + mounts = g_volume_monitor_get_mounts (volume_monitor); + for (l = mounts; l != NULL; l = l->next) + { + mount = l->data; + + if (g_mount_is_shadowed (mount)) + { + continue; + } + + volume = g_mount_get_volume (mount); + if (volume == NULL) + { + mounts_to_check = g_list_prepend (mounts_to_check, g_object_ref (mount)); + } + g_clear_object (&volume); + } + g_list_free_full (mounts, g_object_unref); + + /* actually add mounts to candidates */ + for (l = mounts_to_check; l != NULL; l = l->next) + { + mount = l->data; + + string = g_mount_get_name (mount); + if (string == NULL) + { + continue; + } + + location = g_mount_get_default_location (mount); + uri = g_file_get_uri (location); + candidate = search_hit_candidate_new (uri, string); + candidates = g_list_prepend (candidates, candidate); + + g_free (uri); + g_free (string); + g_object_unref (location); + } + g_list_free_full (mounts_to_check, g_object_unref); + + /* now do the actual string matching */ + candidates = g_list_reverse (candidates); + + for (l = candidates; l != NULL; l = l->next) + { + candidate = l->data; + match = nautilus_query_matches_string (search->query, + candidate->string_for_compare); + + if (match > -1) + { + hit = nautilus_search_hit_new (candidate->uri); + nautilus_search_hit_set_fts_rank (hit, match); + nautilus_search_hit_compute_scores (hit, search->query); + g_hash_table_replace (search->hits, g_strdup (candidate->uri), hit); + } + } + g_list_free_full (candidates, (GDestroyNotify) search_hit_candidate_free); + g_object_unref (volume_monitor); +} + +static NautilusQuery * +shell_query_new (gchar **terms) +{ + NautilusQuery *query; + g_autoptr (GFile) home = NULL; + g_autofree gchar *terms_joined = NULL; + + terms_joined = g_strjoinv (" ", terms); + home = g_file_new_for_path (g_get_home_dir ()); + + query = nautilus_query_new (); + nautilus_query_set_text (query, terms_joined); + nautilus_query_set_location (query, home); + + return query; +} + +static void +execute_search (NautilusShellSearchProvider *self, + GDBusMethodInvocation *invocation, + gchar **terms) +{ + NautilusQuery *query; + PendingSearch *pending_search; + + cancel_current_search (self); + + /* don't attempt searches for a single character */ + if (g_strv_length (terms) == 1 && + g_utf8_strlen (terms[0], -1) == 1) + { + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(as)", NULL)); + return; + } + + query = shell_query_new (terms); + nautilus_query_set_recursive (query, NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY); + nautilus_query_set_show_hidden_files (query, FALSE); + + pending_search = g_slice_new0 (PendingSearch); + pending_search->invocation = g_object_ref (invocation); + pending_search->hits = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + pending_search->query = query; + pending_search->engine = nautilus_search_engine_new (); + pending_search->start_time = g_get_monotonic_time (); + pending_search->self = self; + + g_signal_connect (pending_search->engine, "hits-added", + G_CALLBACK (search_hits_added_cb), pending_search); + g_signal_connect (pending_search->engine, "finished", + G_CALLBACK (search_finished_cb), pending_search); + g_signal_connect (pending_search->engine, "error", + G_CALLBACK (search_error_cb), pending_search); + + self->current_search = pending_search; + g_application_hold (g_application_get_default ()); + + search_add_volumes_and_bookmarks (pending_search); + + /* start searching */ + g_debug ("*** Search engine search started"); + nautilus_search_provider_set_query (NAUTILUS_SEARCH_PROVIDER (pending_search->engine), + query); + nautilus_search_provider_start (NAUTILUS_SEARCH_PROVIDER (pending_search->engine)); +} + +static gboolean +handle_get_initial_result_set (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar **terms, + gpointer user_data) +{ + NautilusShellSearchProvider *self = user_data; + + g_debug ("****** GetInitialResultSet"); + execute_search (self, invocation, terms); + return TRUE; +} + +static gboolean +handle_get_subsearch_result_set (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar **previous_results, + gchar **terms, + gpointer user_data) +{ + NautilusShellSearchProvider *self = user_data; + + g_debug ("****** GetSubSearchResultSet"); + execute_search (self, invocation, terms); + return TRUE; +} + +typedef struct +{ + NautilusShellSearchProvider *self; + + gint64 start_time; + GDBusMethodInvocation *invocation; + + gchar **uris; +} ResultMetasData; + +static void +result_metas_data_free (ResultMetasData *data) +{ + g_clear_object (&data->self); + g_clear_object (&data->invocation); + g_strfreev (data->uris); + + g_slice_free (ResultMetasData, data); +} + +static void +result_metas_return_from_cache (ResultMetasData *data) +{ + GVariantBuilder builder; + GVariant *meta; + gint64 current_time; + gint idx; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}")); + + for (idx = 0; data->uris[idx] != NULL; idx++) + { + meta = g_hash_table_lookup (data->self->metas_cache, + data->uris[idx]); + g_variant_builder_add_value (&builder, meta); + } + + current_time = g_get_monotonic_time (); + g_debug ("*** GetResultMetas completed - time elapsed %dms", + (gint) ((current_time - data->start_time) / 1000)); + + g_dbus_method_invocation_return_value (data->invocation, + g_variant_new ("(aa{sv})", &builder)); +} + +static void +result_list_attributes_ready_cb (GList *file_list, + gpointer user_data) +{ + ResultMetasData *data = user_data; + GVariantBuilder meta; + NautilusFile *file; + GFile *file_location; + GList *l; + gchar *uri, *display_name; + gchar *path, *description; + gchar *thumbnail_path; + GIcon *gicon; + GFile *location; + GVariant *meta_variant; + gint icon_scale; + + icon_scale = gdk_monitor_get_scale_factor (gdk_display_get_monitor (gdk_display_get_default (), 0)); + + for (l = file_list; l != NULL; l = l->next) + { + file = l->data; + g_variant_builder_init (&meta, G_VARIANT_TYPE ("a{sv}")); + + uri = nautilus_file_get_uri (file); + display_name = get_display_name (data->self, file); + file_location = nautilus_file_get_location (file); + path = g_file_get_path (file_location); + description = path ? g_path_get_dirname (path) : NULL; + + g_variant_builder_add (&meta, "{sv}", + "id", g_variant_new_string (uri)); + g_variant_builder_add (&meta, "{sv}", + "name", g_variant_new_string (display_name)); + /* Some backends like trash:/// don't have a path, so we show the uri itself. */ + g_variant_builder_add (&meta, "{sv}", + "description", g_variant_new_string (description ? description : uri)); + + gicon = NULL; + thumbnail_path = nautilus_file_get_thumbnail_path (file); + + if (thumbnail_path != NULL) + { + location = g_file_new_for_path (thumbnail_path); + gicon = g_file_icon_new (location); + + g_free (thumbnail_path); + g_object_unref (location); + } + else + { + gicon = get_gicon (data->self, file); + } + + if (gicon == NULL) + { + gicon = G_ICON (nautilus_file_get_icon_pixbuf (file, 128, TRUE, + icon_scale, + NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS)); + } + + g_variant_builder_add (&meta, "{sv}", + "icon", g_icon_serialize (gicon)); + g_object_unref (gicon); + + meta_variant = g_variant_builder_end (&meta); + g_hash_table_insert (data->self->metas_cache, + g_strdup (uri), g_variant_ref_sink (meta_variant)); + + g_free (display_name); + g_free (path); + g_free (description); + g_free (uri); + } + + result_metas_return_from_cache (data); + result_metas_data_free (data); +} + +static gboolean +handle_get_result_metas (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar **results, + gpointer user_data) +{ + NautilusShellSearchProvider *self = user_data; + GList *missing_files = NULL; + const gchar *uri; + ResultMetasData *data; + gint idx; + + g_debug ("****** GetResultMetas"); + + for (idx = 0; results[idx] != NULL; idx++) + { + uri = results[idx]; + + if (!g_hash_table_lookup (self->metas_cache, uri)) + { + missing_files = g_list_prepend (missing_files, nautilus_file_get_by_uri (uri)); + } + } + + data = g_slice_new0 (ResultMetasData); + data->self = g_object_ref (self); + data->invocation = g_object_ref (invocation); + data->start_time = g_get_monotonic_time (); + data->uris = g_strdupv (results); + + if (missing_files == NULL) + { + result_metas_return_from_cache (data); + result_metas_data_free (data); + return TRUE; + } + + nautilus_file_list_call_when_ready (missing_files, + NAUTILUS_FILE_ATTRIBUTES_FOR_ICON, + NULL, + result_list_attributes_ready_cb, + data); + nautilus_file_list_free (missing_files); + return TRUE; +} + +static gboolean +handle_activate_result (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar *result, + gchar **terms, + guint32 timestamp, + gpointer user_data) +{ + gboolean res; + GFile *file; + + res = gtk_show_uri_on_window (NULL, result, timestamp, NULL); + + if (!res) + { + file = g_file_new_for_uri (result); + g_application_open (g_application_get_default (), &file, 1, ""); + g_object_unref (file); + } + + nautilus_shell_search_provider2_complete_activate_result (skeleton, invocation); + return TRUE; +} + +static gboolean +handle_launch_search (NautilusShellSearchProvider2 *skeleton, + GDBusMethodInvocation *invocation, + gchar **terms, + guint32 timestamp, + gpointer user_data) +{ + GApplication *app = g_application_get_default (); + g_autoptr (NautilusQuery) query = shell_query_new (terms); + NautilusQueryRecursive recursive = location_settings_search_get_recursive (); + + /* + * If no recursive search is enabled, we still want to be able to + * show the same results we presented in the overview when nautilus + * is explicitly launched to access to more results, and thus we perform + * a query showing results coming from index-based search engines. + * Otherwise we respect the global setting for recursivity. + */ + if (recursive == NAUTILUS_QUERY_RECURSIVE_NEVER) + { + nautilus_query_set_recursive (query, + NAUTILUS_QUERY_RECURSIVE_INDEXED_ONLY); + } + else + { + nautilus_query_set_recursive (query, recursive); + } + + nautilus_application_search (NAUTILUS_APPLICATION (app), query); + + nautilus_shell_search_provider2_complete_launch_search (skeleton, invocation); + return TRUE; +} + +static void +search_provider_dispose (GObject *obj) +{ + NautilusShellSearchProvider *self = NAUTILUS_SHELL_SEARCH_PROVIDER (obj); + + g_clear_object (&self->skeleton); + g_hash_table_destroy (self->metas_cache); + cancel_current_search (self); + + G_OBJECT_CLASS (nautilus_shell_search_provider_parent_class)->dispose (obj); +} + +static void +nautilus_shell_search_provider_init (NautilusShellSearchProvider *self) +{ + self->metas_cache = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_variant_unref); + + self->skeleton = nautilus_shell_search_provider2_skeleton_new (); + + g_signal_connect (self->skeleton, "handle-get-initial-result-set", + G_CALLBACK (handle_get_initial_result_set), self); + g_signal_connect (self->skeleton, "handle-get-subsearch-result-set", + G_CALLBACK (handle_get_subsearch_result_set), self); + g_signal_connect (self->skeleton, "handle-get-result-metas", + G_CALLBACK (handle_get_result_metas), self); + g_signal_connect (self->skeleton, "handle-activate-result", + G_CALLBACK (handle_activate_result), self); + g_signal_connect (self->skeleton, "handle-launch-search", + G_CALLBACK (handle_launch_search), self); +} + +static void +nautilus_shell_search_provider_class_init (NautilusShellSearchProviderClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->dispose = search_provider_dispose; +} + +NautilusShellSearchProvider * +nautilus_shell_search_provider_new (void) +{ + return g_object_new (nautilus_shell_search_provider_get_type (), + NULL); +} + +gboolean +nautilus_shell_search_provider_register (NautilusShellSearchProvider *self, + GDBusConnection *connection, + GError **error) +{ + return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton), + connection, + "/org/gnome/Nautilus" PROFILE "/SearchProvider", error); +} + +void +nautilus_shell_search_provider_unregister (NautilusShellSearchProvider *self) +{ + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->skeleton)); +} |