Commit 43e1eea1 authored by Matthias Clasen's avatar Matthias Clasen

Rewrite search to be more similar to nautilus

The main advantage here is that this code works for remote
locations as well.
parent 3b6af254
......@@ -24,11 +24,12 @@
#include "gtkquery.h"
struct _GtkQueryPrivate
struct _GtkQueryPrivate
{
gchar *text;
gchar *location_uri;
GList *mime_types;
gchar **words;
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkQuery, _gtk_query, G_TYPE_OBJECT)
......@@ -37,10 +38,12 @@ static void
finalize (GObject *object)
{
GtkQuery *query;
query = GTK_QUERY (object);
g_free (query->priv->text);
g_free (query->priv->location_uri);
g_strfreev (query->priv->words);
G_OBJECT_CLASS (_gtk_query_parent_class)->finalize (object);
}
......@@ -79,6 +82,9 @@ _gtk_query_set_text (GtkQuery *query,
{
g_free (query->priv->text);
query->priv->text = g_strdup (text);
g_strfreev (query->priv->words);
query->priv->words = NULL;
}
gchar *
......@@ -136,3 +142,49 @@ _gtk_query_add_mime_type (GtkQuery *query,
g_strdup (mime_type));
}
static gchar *
prepare_string_for_compare (const gchar *string)
{
gchar *normalized, *res;
normalized = g_utf8_normalize (string, -1, G_NORMALIZE_NFD);
res = g_utf8_strdown (normalized, -1);
g_free (normalized);
return res;
}
gboolean
gtk_query_matches_string (GtkQuery *query,
const gchar *string)
{
gchar *prepared;
gboolean found;
gint i;
if (!query->priv->text)
return FALSE;
if (!query->priv->words)
{
prepared = prepare_string_for_compare (query->priv->text);
query->priv->words = g_strsplit (prepared, " ", -1);
g_free (prepared);
}
prepared = prepare_string_for_compare (string);
found = TRUE;
for (i = 0; query->priv->words[i]; i++)
{
if (strstr (prepared, query->priv->words[i]) == NULL)
{
found = FALSE;
break;
}
}
g_free (prepared);
return found;
}
......@@ -67,6 +67,9 @@ void _gtk_query_set_mime_types (GtkQuery *query,
void _gtk_query_add_mime_type (GtkQuery *query,
const gchar *mime_type);
gboolean gtk_query_matches_string (GtkQuery *query,
const gchar *string);
G_END_DECLS
#endif /* __GTK_QUERY_H__ */
......@@ -35,16 +35,14 @@
typedef struct
{
GtkSearchEngineSimple *engine;
GCancellable *cancellable;
gchar *path;
gchar **words;
GList *found_list;
GQueue *directories;
gint n_processed_files;
GList *uri_hits;
GList *hits;
/* accessed on both threads: */
volatile gboolean cancelled;
GtkQuery *query;
} SearchThreadData;
......@@ -77,7 +75,7 @@ gtk_search_engine_simple_dispose (GObject *object)
if (priv->active_search)
{
priv->active_search->cancelled = TRUE;
g_cancellable_cancel (priv->active_search->cancellable);
priv->active_search = NULL;
}
......@@ -89,25 +87,22 @@ search_thread_data_new (GtkSearchEngineSimple *engine,
GtkQuery *query)
{
SearchThreadData *data;
char *text, *lower, *uri;
const gchar *uri;
GFile *location;
data = g_new0 (SearchThreadData, 1);
data->engine = g_object_ref (engine);
data->directories = g_queue_new ();
data->query = g_object_ref (query);
uri = _gtk_query_get_location (query);
if (uri != NULL)
{
data->path = g_filename_from_uri (uri, NULL, NULL);
g_free (uri);
}
if (data->path == NULL)
data->path = g_strdup (g_get_home_dir ());
location = g_file_new_for_uri (uri);
else
location = g_file_new_for_path (g_get_home_dir ());
g_queue_push_tail (data->directories, location);
text = _gtk_query_get_text (query);
lower = g_utf8_casefold (text, -1);
data->words = g_strsplit (lower, " ", -1);
g_free (text);
g_free (lower);
data->cancellable = g_cancellable_new ();
return data;
}
......@@ -115,9 +110,12 @@ search_thread_data_new (GtkSearchEngineSimple *engine,
static void
search_thread_data_free (SearchThreadData *data)
{
g_queue_foreach (data->directories, (GFunc)g_object_unref, NULL);
g_queue_free (data->directories);
g_object_unref (data->cancellable);
g_object_unref (data->query);
g_object_unref (data->engine);
g_free (data->path);
g_strfreev (data->words);
g_free (data);
}
......@@ -128,7 +126,7 @@ search_thread_done_idle (gpointer user_data)
data = user_data;
if (!data->cancelled)
if (!g_cancellable_is_cancelled (data->cancellable))
_gtk_search_engine_finished (GTK_SEARCH_ENGINE (data->engine));
data->engine->priv->active_search = NULL;
......@@ -146,15 +144,10 @@ typedef struct
static gboolean
search_thread_add_hits_idle (gpointer user_data)
{
SearchHits *hits;
hits = user_data;
SearchHits *hits = user_data;
if (!hits->thread_data->cancelled)
{
_gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (hits->thread_data->engine),
hits->uris);
}
if (!g_cancellable_is_cancelled (hits->thread_data->cancellable))
_gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (hits->thread_data->engine), hits->uris);
g_list_free_full (hits->uris, g_free);
g_free (hits);
......@@ -169,183 +162,83 @@ send_batch (SearchThreadData *data)
data->n_processed_files = 0;
if (data->uri_hits)
if (data->hits)
{
guint id;
hits = g_new (SearchHits, 1);
hits->uris = data->uri_hits;
hits->uris = data->hits;
hits->thread_data = data;
id = gdk_threads_add_idle (search_thread_add_hits_idle, hits);
g_source_set_name_by_id (id, "[gtk+] search_thread_add_hits_idle");
}
data->uri_hits = NULL;
data->hits = NULL;
}
typedef gboolean (VisitFunc) (const char *fpath, gpointer user_data);
static gboolean process_dir (GFile *dir, GList **new_root_dirs, VisitFunc func, gpointer user_data);
static GList * process_dirs (GList *root_dirs, VisitFunc func, gpointer user_data);
static void breadth_search (gchar *root, VisitFunc func, gpointer user_data);
static void
breadth_search (gchar *root, VisitFunc func, gpointer user_data)
{
GList *subdirs = NULL;
subdirs = g_list_prepend (subdirs, g_file_new_for_path (root));
while (subdirs)
subdirs = process_dirs (subdirs, func, user_data);
}
static GList *
process_dirs (GList *root_dirs, VisitFunc func, gpointer user_data)
visit_directory (GFile *dir, SearchThreadData *data)
{
SearchThreadData *data = (SearchThreadData*) user_data;
GList *new_root_dirs = NULL;
GList *root;
gboolean keep_going = TRUE;
for (root = root_dirs; root; root = g_list_next (root))
{
GFile *dir = (GFile *) root->data;
/* Even if cancelled or stopped, we keep looping to unref the dirs */
if (keep_going && !data->cancelled)
keep_going = process_dir (dir, &new_root_dirs, func, user_data);
g_object_unref (dir);
}
if (!keep_going || data->cancelled)
{
g_list_free_full (new_root_dirs, g_object_unref);
new_root_dirs = NULL;
}
g_list_free (root_dirs);
return g_list_reverse (new_root_dirs);
}
GFileEnumerator *enumerator;
GFileInfo *info;
GFile *child;
const gchar *display_name;
enumerator = g_file_enumerate_children (dir,
G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
data->cancellable, NULL);
if (enumerator == NULL)
return;
static gboolean
process_dir (GFile *dir, GList **new_root_dirs, VisitFunc func, gpointer user_data)
{
GFileEnumerator *direnum;
gchar *dirpath;
SearchThreadData *data = (SearchThreadData*) user_data;
direnum = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME ","
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL, NULL);
if (direnum == NULL || data->cancelled)
return FALSE;
dirpath = g_file_get_path (dir);
while (TRUE)
while (g_file_enumerator_iterate (enumerator, &info, &child, data->cancellable, NULL))
{
GFileInfo *info;
gchar *fullname;
const gchar *basename;
gboolean keep_going;
if (info == NULL)
break;
keep_going = g_file_enumerator_iterate (direnum, &info, NULL, NULL, NULL);
if (!keep_going || info == NULL || data->cancelled)
{
g_object_unref (direnum);
g_free (dirpath);
return keep_going;
}
display_name = g_file_info_get_display_name (info);
if (display_name == NULL)
continue;
if (g_file_info_get_is_hidden (info))
continue;
basename = g_file_info_get_name (info);
fullname = g_build_filename (dirpath, basename, NULL);
keep_going = func ((const char *) fullname, user_data);
g_free (fullname);
if (gtk_query_matches_string (data->query, display_name))
data->hits = g_list_prepend (data->hits, g_file_get_uri (child));
if (!keep_going)
{
g_object_unref (direnum);
g_free (dirpath);
return FALSE;
}
data->n_processed_files++;
if (data->n_processed_files > BATCH_SIZE)
send_batch (data);
if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
continue;
*new_root_dirs = g_list_prepend (*new_root_dirs,
g_file_get_child (dir, basename));
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
g_queue_push_tail (data->directories, g_object_ref (child));
}
}
static int
search_visit_func (const char *fpath, gpointer user_data)
{
SearchThreadData *data;
gint i;
gchar *display_basename;
gchar *lower_name;
gchar *uri;
gboolean hit;
data = (SearchThreadData*) user_data;
if (data->cancelled)
return FALSE;
display_basename = g_filename_display_basename (fpath);
lower_name = g_utf8_casefold (display_basename, -1);
g_free (display_basename);
hit = TRUE;
for (i = 0; data->words[i] != NULL; i++)
{
if (strstr (lower_name, data->words[i]) == NULL)
{
hit = FALSE;
break;
}
}
g_free (lower_name);
if (hit)
{
uri = g_filename_to_uri (fpath, NULL, NULL);
data->uri_hits = g_list_prepend (data->uri_hits, uri);
}
data->n_processed_files++;
if (data->n_processed_files > BATCH_SIZE)
send_batch (data);
return TRUE;
g_object_unref (enumerator);
}
static gpointer
search_thread_func (gpointer user_data)
{
guint id;
SearchThreadData *data;
GFile *dir;
guint id;
data = user_data;
breadth_search (data->path, search_visit_func, data);
while (!g_cancellable_is_cancelled (data->cancellable) &&
(dir = g_queue_pop_head (data->directories)) != NULL)
{
visit_directory (dir, data);
g_object_unref (dir);
}
send_batch (data);
if (!g_cancellable_is_cancelled (data->cancellable))
send_batch (data);
id = gdk_threads_add_idle (search_thread_done_idle, data);
g_source_set_name_by_id (id, "[gtk+] search_thread_done_idle");
......@@ -383,7 +276,7 @@ gtk_search_engine_simple_stop (GtkSearchEngine *engine)
if (simple->priv->active_search != NULL)
{
simple->priv->active_search->cancelled = TRUE;
g_cancellable_cancel (simple->priv->active_search->cancellable);
simple->priv->active_search = NULL;
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment