diff options
Diffstat (limited to 'libcaja-private/caja-search-directory.c')
-rw-r--r-- | libcaja-private/caja-search-directory.c | 934 |
1 files changed, 934 insertions, 0 deletions
diff --git a/libcaja-private/caja-search-directory.c b/libcaja-private/caja-search-directory.c new file mode 100644 index 00000000..7c1d7660 --- /dev/null +++ b/libcaja-private/caja-search-directory.c @@ -0,0 +1,934 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* + Copyright (C) 2005 Novell, Inc + + This program 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. + + This program 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, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Anders Carlsson <[email protected]> +*/ + +#include <config.h> +#include "caja-search-directory.h" +#include "caja-search-directory-file.h" + +#include "caja-directory-private.h" +#include "caja-file.h" +#include "caja-file-private.h" +#include "caja-file-utilities.h" +#include "caja-search-engine.h" +#include <eel/eel-glib-extensions.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <string.h> +#include <sys/time.h> + +struct CajaSearchDirectoryDetails +{ + CajaQuery *query; + char *saved_search_uri; + gboolean modified; + + CajaSearchEngine *engine; + + gboolean search_running; + gboolean search_finished; + + GList *files; + GHashTable *file_hash; + + GList *monitor_list; + GList *callback_list; + GList *pending_callback_list; +}; + +typedef struct +{ + gboolean monitor_hidden_files; + gboolean monitor_backup_files; + CajaFileAttributes monitor_attributes; + + gconstpointer client; +} SearchMonitor; + +typedef struct +{ + CajaSearchDirectory *search_directory; + + CajaDirectoryCallback callback; + gpointer callback_data; + + CajaFileAttributes wait_for_attributes; + gboolean wait_for_file_list; + GList *file_list; + GHashTable *non_ready_hash; +} SearchCallback; + +G_DEFINE_TYPE (CajaSearchDirectory, caja_search_directory, + CAJA_TYPE_DIRECTORY); + +static void search_engine_hits_added (CajaSearchEngine *engine, GList *hits, CajaSearchDirectory *search); +static void search_engine_hits_subtracted (CajaSearchEngine *engine, GList *hits, CajaSearchDirectory *search); +static void search_engine_finished (CajaSearchEngine *engine, CajaSearchDirectory *search); +static void search_engine_error (CajaSearchEngine *engine, const char *error, CajaSearchDirectory *search); +static void search_callback_file_ready_callback (CajaFile *file, gpointer data); +static void file_changed (CajaFile *file, CajaSearchDirectory *search); + +static void +ensure_search_engine (CajaSearchDirectory *search) +{ + if (!search->details->engine) + { + search->details->engine = caja_search_engine_new (); + g_signal_connect (search->details->engine, "hits-added", + G_CALLBACK (search_engine_hits_added), + search); + g_signal_connect (search->details->engine, "hits-subtracted", + G_CALLBACK (search_engine_hits_subtracted), + search); + g_signal_connect (search->details->engine, "finished", + G_CALLBACK (search_engine_finished), + search); + g_signal_connect (search->details->engine, "error", + G_CALLBACK (search_engine_error), + search); + } +} + +static void +reset_file_list (CajaSearchDirectory *search) +{ + GList *list, *monitor_list; + CajaFile *file; + SearchMonitor *monitor; + + /* Remove file connections */ + for (list = search->details->files; list != NULL; list = list->next) + { + file = list->data; + + /* Disconnect change handler */ + g_signal_handlers_disconnect_by_func (file, file_changed, search); + + /* Remove monitors */ + for (monitor_list = search->details->monitor_list; monitor_list; + monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + caja_file_monitor_remove (file, monitor); + } + } + + caja_file_list_free (search->details->files); + search->details->files = NULL; +} + +static void +start_or_stop_search_engine (CajaSearchDirectory *search, gboolean adding) +{ + if (adding && (search->details->monitor_list || + search->details->pending_callback_list) && + search->details->query && + !search->details->search_running) + { + /* We need to start the search engine */ + search->details->search_running = TRUE; + search->details->search_finished = FALSE; + ensure_search_engine (search); + caja_search_engine_set_query (search->details->engine, search->details->query); + + reset_file_list (search); + + caja_search_engine_start (search->details->engine); + } + else if (!adding && !search->details->monitor_list && + !search->details->pending_callback_list && + search->details->engine && + search->details->search_running) + { + search->details->search_running = FALSE; + caja_search_engine_stop (search->details->engine); + + reset_file_list (search); + } + +} + +static void +file_changed (CajaFile *file, CajaSearchDirectory *search) +{ + GList list; + + list.data = file; + list.next = NULL; + + caja_directory_emit_files_changed (CAJA_DIRECTORY (search), &list); +} + +static void +search_monitor_add (CajaDirectory *directory, + gconstpointer client, + gboolean monitor_hidden_files, + gboolean monitor_backup_files, + CajaFileAttributes file_attributes, + CajaDirectoryCallback callback, + gpointer callback_data) +{ + GList *list; + SearchMonitor *monitor; + CajaSearchDirectory *search; + CajaFile *file; + + search = CAJA_SEARCH_DIRECTORY (directory); + + monitor = g_new0 (SearchMonitor, 1); + monitor->monitor_hidden_files = monitor_hidden_files; + monitor->monitor_backup_files = monitor_backup_files; + monitor->monitor_attributes = file_attributes; + monitor->client = client; + + search->details->monitor_list = g_list_prepend (search->details->monitor_list, monitor); + + if (callback != NULL) + { + (* callback) (directory, search->details->files, callback_data); + } + + for (list = search->details->files; list != NULL; list = list->next) + { + file = list->data; + + /* Add monitors */ + caja_file_monitor_add (file, monitor, file_attributes); + } + + start_or_stop_search_engine (search, TRUE); +} + +static void +search_monitor_remove_file_monitors (SearchMonitor *monitor, CajaSearchDirectory *search) +{ + GList *list; + CajaFile *file; + + for (list = search->details->files; list != NULL; list = list->next) + { + file = list->data; + + caja_file_monitor_remove (file, monitor); + } +} + +static void +search_monitor_destroy (SearchMonitor *monitor, CajaSearchDirectory *search) +{ + search_monitor_remove_file_monitors (monitor, search); + + g_free (monitor); +} + +static void +search_monitor_remove (CajaDirectory *directory, + gconstpointer client) +{ + CajaSearchDirectory *search; + SearchMonitor *monitor; + GList *list; + + search = CAJA_SEARCH_DIRECTORY (directory); + + for (list = search->details->monitor_list; list != NULL; list = list->next) + { + monitor = list->data; + + if (monitor->client == client) + { + search->details->monitor_list = g_list_delete_link (search->details->monitor_list, list); + + search_monitor_destroy (monitor, search); + + break; + } + } + + start_or_stop_search_engine (search, FALSE); +} + +static void +cancel_call_when_ready (gpointer key, gpointer value, gpointer user_data) +{ + SearchCallback *search_callback; + CajaFile *file; + + file = key; + search_callback = user_data; + + caja_file_cancel_call_when_ready (file, search_callback_file_ready_callback, + search_callback); +} + +static void +search_callback_destroy (SearchCallback *search_callback) +{ + if (search_callback->non_ready_hash) + { + g_hash_table_foreach (search_callback->non_ready_hash, cancel_call_when_ready, search_callback); + g_hash_table_destroy (search_callback->non_ready_hash); + } + + caja_file_list_free (search_callback->file_list); + + g_free (search_callback); +} + +static void +search_callback_invoke_and_destroy (SearchCallback *search_callback) +{ + search_callback->callback (CAJA_DIRECTORY (search_callback->search_directory), + search_callback->file_list, + search_callback->callback_data); + + search_callback->search_directory->details->callback_list = + g_list_remove (search_callback->search_directory->details->callback_list, search_callback); + + search_callback_destroy (search_callback); +} + +static void +search_callback_file_ready_callback (CajaFile *file, gpointer data) +{ + SearchCallback *search_callback = data; + + g_hash_table_remove (search_callback->non_ready_hash, file); + + if (g_hash_table_size (search_callback->non_ready_hash) == 0) + { + search_callback_invoke_and_destroy (search_callback); + } +} + +static void +search_callback_add_file_callbacks (SearchCallback *callback) +{ + GList *file_list_copy, *list; + CajaFile *file; + + file_list_copy = g_list_copy (callback->file_list); + + for (list = file_list_copy; list != NULL; list = list->next) + { + file = list->data; + + caja_file_call_when_ready (file, + callback->wait_for_attributes, + search_callback_file_ready_callback, + callback); + } + g_list_free (file_list_copy); +} + +static SearchCallback * +search_callback_find (CajaSearchDirectory *search, CajaDirectoryCallback callback, gpointer callback_data) +{ + SearchCallback *search_callback; + GList *list; + + for (list = search->details->callback_list; list != NULL; list = list->next) + { + search_callback = list->data; + + if (search_callback->callback == callback && + search_callback->callback_data == callback_data) + { + return search_callback; + } + } + + return NULL; +} + +static SearchCallback * +search_callback_find_pending (CajaSearchDirectory *search, CajaDirectoryCallback callback, gpointer callback_data) +{ + SearchCallback *search_callback; + GList *list; + + for (list = search->details->pending_callback_list; list != NULL; list = list->next) + { + search_callback = list->data; + + if (search_callback->callback == callback && + search_callback->callback_data == callback_data) + { + return search_callback; + } + } + + return NULL; +} + +static GHashTable * +file_list_to_hash_table (GList *file_list) +{ + GList *list; + GHashTable *table; + + if (!file_list) + return NULL; + + table = g_hash_table_new (NULL, NULL); + + for (list = file_list; list != NULL; list = list->next) + { + g_hash_table_insert (table, list->data, list->data); + } + + return table; +} + +static void +search_call_when_ready (CajaDirectory *directory, + CajaFileAttributes file_attributes, + gboolean wait_for_file_list, + CajaDirectoryCallback callback, + gpointer callback_data) +{ + CajaSearchDirectory *search; + SearchCallback *search_callback; + + search = CAJA_SEARCH_DIRECTORY (directory); + + search_callback = search_callback_find (search, callback, callback_data); + if (search_callback == NULL) + { + search_callback = search_callback_find_pending (search, callback, callback_data); + } + + if (search_callback) + { + g_warning ("tried to add a new callback while an old one was pending"); + return; + } + + search_callback = g_new0 (SearchCallback, 1); + search_callback->search_directory = search; + search_callback->callback = callback; + search_callback->callback_data = callback_data; + search_callback->wait_for_attributes = file_attributes; + search_callback->wait_for_file_list = wait_for_file_list; + + if (wait_for_file_list && !search->details->search_finished) + { + /* Add it to the pending callback list, which will be + * processed when the directory has finished loading + */ + search->details->pending_callback_list = + g_list_prepend (search->details->pending_callback_list, search_callback); + + /* We might need to start the search engine */ + start_or_stop_search_engine (search, TRUE); + } + else + { + search_callback->file_list = caja_file_list_copy (search->details->files); + search_callback->non_ready_hash = file_list_to_hash_table (search->details->files); + + if (!search_callback->non_ready_hash) + { + /* If there are no ready files, we invoke the callback + with an empty list. + */ + search_callback_invoke_and_destroy (search_callback); + } + else + { + search->details->callback_list = g_list_prepend (search->details->callback_list, search_callback); + search_callback_add_file_callbacks (search_callback); + } + } +} + +static void +search_cancel_callback (CajaDirectory *directory, + CajaDirectoryCallback callback, + gpointer callback_data) +{ + CajaSearchDirectory *search; + SearchCallback *search_callback; + + search = CAJA_SEARCH_DIRECTORY (directory); + search_callback = search_callback_find (search, callback, callback_data); + + if (search_callback) + { + search->details->callback_list = g_list_remove (search->details->callback_list, search_callback); + + search_callback_destroy (search_callback); + + return; + } + + /* Check for a pending callback */ + search_callback = search_callback_find_pending (search, callback, callback_data); + + if (search_callback) + { + search->details->pending_callback_list = g_list_remove (search->details->pending_callback_list, search_callback); + + search_callback_destroy (search_callback); + + /* We might need to stop the search engine now */ + start_or_stop_search_engine (search, FALSE); + } +} + + +static void +search_engine_hits_added (CajaSearchEngine *engine, GList *hits, + CajaSearchDirectory *search) +{ + GList *hit_list; + GList *file_list; + CajaFile *file; + char *uri; + SearchMonitor *monitor; + GList *monitor_list; + + file_list = NULL; + + for (hit_list = hits; hit_list != NULL; hit_list = hit_list->next) + { + uri = hit_list->data; + + if (g_str_has_suffix (uri, CAJA_SAVED_SEARCH_EXTENSION)) + { + /* Never return saved searches themselves as hits */ + continue; + } + + file = caja_file_get_by_uri (uri); + + for (monitor_list = search->details->monitor_list; monitor_list; monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + + /* Add monitors */ + caja_file_monitor_add (file, monitor, monitor->monitor_attributes); + } + + g_signal_connect (file, "changed", G_CALLBACK (file_changed), search), + + file_list = g_list_prepend (file_list, file); + } + + search->details->files = g_list_concat (search->details->files, file_list); + + caja_directory_emit_files_added (CAJA_DIRECTORY (search), file_list); + + file = caja_directory_get_corresponding_file (CAJA_DIRECTORY (search)); + caja_file_emit_changed (file); + caja_file_unref (file); +} + +static void +search_engine_hits_subtracted (CajaSearchEngine *engine, GList *hits, + CajaSearchDirectory *search) +{ + GList *hit_list; + GList *monitor_list; + SearchMonitor *monitor; + GList *file_list; + char *uri; + CajaFile *file; + + file_list = NULL; + + for (hit_list = hits; hit_list != NULL; hit_list = hit_list->next) + { + uri = hit_list->data; + file = caja_file_get_by_uri (uri); + + for (monitor_list = search->details->monitor_list; monitor_list; + monitor_list = monitor_list->next) + { + monitor = monitor_list->data; + /* Remove monitors */ + caja_file_monitor_remove (file, monitor); + } + + g_signal_handlers_disconnect_by_func (file, file_changed, search); + + search->details->files = g_list_remove (search->details->files, file); + + file_list = g_list_prepend (file_list, file); + } + + caja_directory_emit_files_changed (CAJA_DIRECTORY (search), file_list); + + caja_file_list_free (file_list); + + file = caja_directory_get_corresponding_file (CAJA_DIRECTORY (search)); + caja_file_emit_changed (file); + caja_file_unref (file); +} + +static void +search_callback_add_pending_file_callbacks (SearchCallback *callback) +{ + callback->file_list = caja_file_list_copy (callback->search_directory->details->files); + callback->non_ready_hash = file_list_to_hash_table (callback->search_directory->details->files); + + search_callback_add_file_callbacks (callback); +} + +static void +search_engine_error (CajaSearchEngine *engine, const char *error_message, CajaSearchDirectory *search) +{ + GError *error; + + error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, + error_message); + caja_directory_emit_load_error (CAJA_DIRECTORY (search), + error); + g_error_free (error); +} + +static void +search_engine_finished (CajaSearchEngine *engine, CajaSearchDirectory *search) +{ + search->details->search_finished = TRUE; + + caja_directory_emit_done_loading (CAJA_DIRECTORY (search)); + + /* Add all file callbacks */ + g_list_foreach (search->details->pending_callback_list, + (GFunc)search_callback_add_pending_file_callbacks, NULL); + search->details->callback_list = g_list_concat (search->details->callback_list, + search->details->pending_callback_list); + + g_list_free (search->details->pending_callback_list); + search->details->pending_callback_list = NULL; +} + +static void +search_force_reload (CajaDirectory *directory) +{ + CajaSearchDirectory *search; + + search = CAJA_SEARCH_DIRECTORY (directory); + + if (!search->details->query) + { + return; + } + + search->details->search_finished = FALSE; + + if (!search->details->engine) + { + return; + } + + /* Remove file monitors */ + reset_file_list (search); + + if (search->details->search_running) + { + caja_search_engine_stop (search->details->engine); + caja_search_engine_set_query (search->details->engine, search->details->query); + caja_search_engine_start (search->details->engine); + } +} + +static gboolean +search_are_all_files_seen (CajaDirectory *directory) +{ + CajaSearchDirectory *search; + + search = CAJA_SEARCH_DIRECTORY (directory); + + return (!search->details->query || + search->details->search_finished); +} + +static gboolean +search_contains_file (CajaDirectory *directory, + CajaFile *file) +{ + CajaSearchDirectory *search; + + search = CAJA_SEARCH_DIRECTORY (directory); + + /* FIXME: Maybe put the files in a hash */ + return (g_list_find (search->details->files, file) != NULL); +} + +static GList * +search_get_file_list (CajaDirectory *directory) +{ + CajaSearchDirectory *search; + + search = CAJA_SEARCH_DIRECTORY (directory); + + return caja_file_list_copy (search->details->files); +} + + +static gboolean +search_is_editable (CajaDirectory *directory) +{ + return FALSE; +} + +static void +search_dispose (GObject *object) +{ + CajaSearchDirectory *search; + GList *list; + + search = CAJA_SEARCH_DIRECTORY (object); + + /* Remove search monitors */ + if (search->details->monitor_list) + { + for (list = search->details->monitor_list; list != NULL; list = list->next) + { + search_monitor_destroy ((SearchMonitor *)list->data, search); + } + + g_list_free (search->details->monitor_list); + search->details->monitor_list = NULL; + } + + reset_file_list (search); + + if (search->details->callback_list) + { + /* Remove callbacks */ + g_list_foreach (search->details->callback_list, + (GFunc)search_callback_destroy, NULL); + g_list_free (search->details->callback_list); + search->details->callback_list = NULL; + } + + if (search->details->pending_callback_list) + { + g_list_foreach (search->details->pending_callback_list, + (GFunc)search_callback_destroy, NULL); + g_list_free (search->details->pending_callback_list); + search->details->pending_callback_list = NULL; + } + + if (search->details->query) + { + g_object_unref (search->details->query); + search->details->query = NULL; + } + + if (search->details->engine) + { + if (search->details->search_running) + { + caja_search_engine_stop (search->details->engine); + } + + g_object_unref (search->details->engine); + search->details->engine = NULL; + } + + G_OBJECT_CLASS (caja_search_directory_parent_class)->dispose (object); +} + +static void +search_finalize (GObject *object) +{ + CajaSearchDirectory *search; + + search = CAJA_SEARCH_DIRECTORY (object); + + g_free (search->details->saved_search_uri); + + g_free (search->details); + + G_OBJECT_CLASS (caja_search_directory_parent_class)->finalize (object); +} + +static void +caja_search_directory_init (CajaSearchDirectory *search) +{ + search->details = g_new0 (CajaSearchDirectoryDetails, 1); +} + +static void +caja_search_directory_class_init (CajaSearchDirectoryClass *class) +{ + CajaDirectoryClass *directory_class; + + G_OBJECT_CLASS (class)->dispose = search_dispose; + G_OBJECT_CLASS (class)->finalize = search_finalize; + + directory_class = CAJA_DIRECTORY_CLASS (class); + + directory_class->are_all_files_seen = search_are_all_files_seen; + directory_class->contains_file = search_contains_file; + directory_class->force_reload = search_force_reload; + directory_class->call_when_ready = search_call_when_ready; + directory_class->cancel_callback = search_cancel_callback; + + directory_class->file_monitor_add = search_monitor_add; + directory_class->file_monitor_remove = search_monitor_remove; + + directory_class->get_file_list = search_get_file_list; + directory_class->is_editable = search_is_editable; +} + +char * +caja_search_directory_generate_new_uri (void) +{ + static int counter = 0; + char *uri; + + uri = g_strdup_printf (EEL_SEARCH_URI"//%d/", counter++); + + return uri; +} + + +void +caja_search_directory_set_query (CajaSearchDirectory *search, + CajaQuery *query) +{ + CajaDirectory *dir; + CajaFile *as_file; + + if (search->details->query != query) + { + search->details->modified = TRUE; + } + + if (query) + { + g_object_ref (query); + } + + if (search->details->query) + { + g_object_unref (search->details->query); + } + + search->details->query = query; + + dir = CAJA_DIRECTORY (search); + as_file = dir->details->as_file; + if (as_file != NULL) + { + caja_search_directory_file_update_display_name (CAJA_SEARCH_DIRECTORY_FILE (as_file)); + } +} + +CajaQuery * +caja_search_directory_get_query (CajaSearchDirectory *search) +{ + if (search->details->query != NULL) + { + return g_object_ref (search->details->query); + } + + return NULL; +} + +CajaSearchDirectory * +caja_search_directory_new_from_saved_search (const char *uri) +{ + CajaSearchDirectory *search; + CajaQuery *query; + char *file; + + search = CAJA_SEARCH_DIRECTORY (g_object_new (CAJA_TYPE_SEARCH_DIRECTORY, NULL)); + + search->details->saved_search_uri = g_strdup (uri); + + file = g_filename_from_uri (uri, NULL, NULL); + if (file != NULL) + { + query = caja_query_load (file); + if (query != NULL) + { + caja_search_directory_set_query (search, query); + g_object_unref (query); + } + g_free (file); + } + else + { + g_warning ("Non-local saved searches not supported"); + } + + search->details->modified = FALSE; + return search; +} + +gboolean +caja_search_directory_is_saved_search (CajaSearchDirectory *search) +{ + return search->details->saved_search_uri != NULL; +} + +gboolean +caja_search_directory_is_modified (CajaSearchDirectory *search) +{ + return search->details->modified; +} + +gboolean +caja_search_directory_is_indexed (CajaSearchDirectory *search) +{ + ensure_search_engine (search); + return caja_search_engine_is_indexed (search->details->engine); +} + + +void +caja_search_directory_save_to_file (CajaSearchDirectory *search, + const char *save_file_uri) +{ + char *file; + + file = g_filename_from_uri (save_file_uri, NULL, NULL); + if (file == NULL) + { + return; + } + + if (search->details->query != NULL) + { + caja_query_save (search->details->query, file); + } + + g_free (file); +} + +void +caja_search_directory_save_search (CajaSearchDirectory *search) +{ + if (search->details->saved_search_uri == NULL) + { + return; + } + + caja_search_directory_save_to_file (search, + search->details->saved_search_uri); + search->details->modified = FALSE; +} |