summaryrefslogtreecommitdiff
path: root/libcaja-private/caja-merged-directory.c
diff options
context:
space:
mode:
Diffstat (limited to 'libcaja-private/caja-merged-directory.c')
-rw-r--r--libcaja-private/caja-merged-directory.c735
1 files changed, 735 insertions, 0 deletions
diff --git a/libcaja-private/caja-merged-directory.c b/libcaja-private/caja-merged-directory.c
new file mode 100644
index 00000000..328876cf
--- /dev/null
+++ b/libcaja-private/caja-merged-directory.c
@@ -0,0 +1,735 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-merged-directory.c: Subclass of CajaDirectory to implement the
+ virtual merged directory.
+
+ Copyright (C) 1999, 2000 Eazel, 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: Darin Adler <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-merged-directory.h"
+
+#include "caja-directory-private.h"
+#include "caja-directory-notify.h"
+#include "caja-file.h"
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-gtk-macros.h>
+#include <gtk/gtk.h>
+
+struct CajaMergedDirectoryDetails
+{
+ GList *directories;
+ GList *directories_not_done_loading;
+ GHashTable *callbacks;
+ GHashTable *monitors;
+};
+
+typedef struct
+{
+ CajaMergedDirectory *merged;
+ CajaDirectoryCallback callback;
+ gpointer callback_data;
+
+ CajaFileAttributes wait_for_attributes;
+ gboolean wait_for_file_list;
+
+ GList *non_ready_directories;
+ GList *merged_file_list;
+} MergedCallback;
+
+typedef struct
+{
+ CajaMergedDirectory *merged;
+
+ gboolean monitor_hidden_files;
+ gboolean monitor_backup_files;
+ CajaFileAttributes monitor_attributes;
+} MergedMonitor;
+
+enum
+{
+ ADD_REAL_DIRECTORY,
+ REMOVE_REAL_DIRECTORY,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (CajaMergedDirectory, caja_merged_directory,
+ CAJA_TYPE_DIRECTORY);
+#define parent_class caja_merged_directory_parent_class
+
+static guint
+merged_callback_hash (gconstpointer merged_callback_as_pointer)
+{
+ const MergedCallback *merged_callback;
+
+ merged_callback = merged_callback_as_pointer;
+ return GPOINTER_TO_UINT (merged_callback->callback)
+ ^ GPOINTER_TO_UINT (merged_callback->callback_data);
+}
+
+static gboolean
+merged_callback_equal (gconstpointer merged_callback_as_pointer,
+ gconstpointer merged_callback_as_pointer_2)
+{
+ const MergedCallback *merged_callback, *merged_callback_2;
+
+ merged_callback = merged_callback_as_pointer;
+ merged_callback_2 = merged_callback_as_pointer_2;
+
+ return merged_callback->callback == merged_callback_2->callback
+ && merged_callback->callback_data == merged_callback_2->callback_data;
+}
+
+static void
+merged_callback_destroy (MergedCallback *merged_callback)
+{
+ g_assert (merged_callback != NULL);
+ g_assert (CAJA_IS_MERGED_DIRECTORY (merged_callback->merged));
+
+ g_list_free (merged_callback->non_ready_directories);
+ caja_file_list_free (merged_callback->merged_file_list);
+ g_free (merged_callback);
+}
+
+static void
+merged_callback_check_done (MergedCallback *merged_callback)
+{
+ /* Check if we are ready. */
+ if (merged_callback->non_ready_directories != NULL)
+ {
+ return;
+ }
+
+ /* Remove from the hash table before sending it. */
+ g_hash_table_remove (merged_callback->merged->details->callbacks, merged_callback);
+
+ /* We are ready, so do the real callback. */
+ (* merged_callback->callback) (CAJA_DIRECTORY (merged_callback->merged),
+ merged_callback->merged_file_list,
+ merged_callback->callback_data);
+
+ /* And we are done. */
+ merged_callback_destroy (merged_callback);
+}
+
+static void
+merged_callback_remove_directory (MergedCallback *merged_callback,
+ CajaDirectory *directory)
+{
+ merged_callback->non_ready_directories = g_list_remove
+ (merged_callback->non_ready_directories, directory);
+ merged_callback_check_done (merged_callback);
+}
+
+static void
+directory_ready_callback (CajaDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ MergedCallback *merged_callback;
+
+ g_assert (CAJA_IS_DIRECTORY (directory));
+ g_assert (callback_data != NULL);
+
+ merged_callback = callback_data;
+ g_assert (g_list_find (merged_callback->non_ready_directories, directory) != NULL);
+
+ /* Update based on this call. */
+ merged_callback->merged_file_list = g_list_concat
+ (merged_callback->merged_file_list,
+ caja_file_list_copy (files));
+
+ /* Check if we are ready. */
+ merged_callback_remove_directory (merged_callback, directory);
+}
+
+static void
+merged_call_when_ready (CajaDirectory *directory,
+ CajaFileAttributes file_attributes,
+ gboolean wait_for_file_list,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaMergedDirectory *merged;
+ MergedCallback search_key, *merged_callback;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Check to be sure we aren't overwriting. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ if (g_hash_table_lookup (merged->details->callbacks, &search_key) != NULL)
+ {
+ g_warning ("tried to add a new callback while an old one was pending");
+ return;
+ }
+
+ /* Create a merged_callback record. */
+ merged_callback = g_new0 (MergedCallback, 1);
+ merged_callback->merged = merged;
+ merged_callback->callback = callback;
+ merged_callback->callback_data = callback_data;
+ merged_callback->wait_for_attributes = file_attributes;
+ merged_callback->wait_for_file_list = wait_for_file_list;
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ merged_callback->non_ready_directories = g_list_prepend
+ (merged_callback->non_ready_directories, node->data);
+ }
+
+ /* Put it in the hash table. */
+ g_hash_table_insert (merged->details->callbacks,
+ merged_callback, merged_callback);
+
+ /* Handle the pathological case where there are no directories. */
+ if (merged->details->directories == NULL)
+ {
+ merged_callback_check_done (merged_callback);
+ }
+
+ /* Now tell all the directories about it. */
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ caja_directory_call_when_ready
+ (node->data,
+ merged_callback->wait_for_attributes,
+ merged_callback->wait_for_file_list,
+ directory_ready_callback, merged_callback);
+ }
+}
+
+static void
+merged_cancel_callback (CajaDirectory *directory,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaMergedDirectory *merged;
+ MergedCallback search_key, *merged_callback;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Find the entry in the table. */
+ search_key.callback = callback;
+ search_key.callback_data = callback_data;
+ merged_callback = g_hash_table_lookup (merged->details->callbacks, &search_key);
+ if (merged_callback == NULL)
+ {
+ return;
+ }
+
+ /* Remove from the hash table before working with it. */
+ g_hash_table_remove (merged_callback->merged->details->callbacks, merged_callback);
+
+ /* Tell all the directories to cancel the call. */
+ for (node = merged_callback->non_ready_directories; node != NULL; node = node->next)
+ {
+ caja_directory_cancel_callback
+ (node->data,
+ directory_ready_callback, merged_callback);
+ }
+ merged_callback_destroy (merged_callback);
+}
+
+static void
+build_merged_callback_list (CajaDirectory *directory,
+ GList *file_list,
+ gpointer callback_data)
+{
+ GList **merged_list;
+
+ merged_list = callback_data;
+ *merged_list = g_list_concat (*merged_list,
+ caja_file_list_copy (file_list));
+}
+
+/* Create a monitor on each of the directories in the list. */
+static void
+merged_monitor_add (CajaDirectory *directory,
+ gconstpointer client,
+ gboolean monitor_hidden_files,
+ gboolean monitor_backup_files,
+ CajaFileAttributes file_attributes,
+ CajaDirectoryCallback callback,
+ gpointer callback_data)
+{
+ CajaMergedDirectory *merged;
+ MergedMonitor *monitor;
+ GList *node;
+ GList *merged_callback_list;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Map the client to a unique value so this doesn't interfere
+ * with direct monitoring of the directory by the same client.
+ */
+ monitor = g_hash_table_lookup (merged->details->monitors, client);
+ if (monitor != NULL)
+ {
+ g_assert (monitor->merged == merged);
+ }
+ else
+ {
+ monitor = g_new0 (MergedMonitor, 1);
+ monitor->merged = merged;
+ g_hash_table_insert (merged->details->monitors,
+ (gpointer) client, monitor);
+ }
+ monitor->monitor_hidden_files = monitor_hidden_files;
+ monitor->monitor_backup_files = monitor_backup_files;
+ monitor->monitor_attributes = file_attributes;
+
+ /* Call through to the real directory add calls. */
+ merged_callback_list = NULL;
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ caja_directory_file_monitor_add
+ (node->data, monitor,
+ monitor_hidden_files, monitor_backup_files,
+ file_attributes,
+ build_merged_callback_list, &merged_callback_list);
+ }
+ if (callback != NULL)
+ {
+ (* callback) (directory, merged_callback_list, callback_data);
+ }
+ caja_file_list_free (merged_callback_list);
+}
+
+static void
+merged_monitor_destroy (CajaMergedDirectory *merged, MergedMonitor *monitor)
+{
+ GList *node;
+
+ /* Call through to the real directory remove calls. */
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ caja_directory_file_monitor_remove (node->data, monitor);
+ }
+
+ g_free (monitor);
+}
+
+/* Remove the monitor from each of the directories in the list. */
+static void
+merged_monitor_remove (CajaDirectory *directory,
+ gconstpointer client)
+{
+ CajaMergedDirectory *merged;
+ MergedMonitor *monitor;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Map the client to the value used by the earlier add call. */
+ monitor = g_hash_table_lookup (merged->details->monitors, client);
+ if (monitor == NULL)
+ {
+ return;
+ }
+ g_hash_table_remove (merged->details->monitors, client);
+
+ merged_monitor_destroy (merged, monitor);
+}
+
+static void
+merged_force_reload (CajaDirectory *directory)
+{
+ CajaMergedDirectory *merged;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ /* Call through to the real force_reload calls. */
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ caja_directory_force_reload (node->data);
+ }
+}
+
+/* Return true if any directory in the list does. */
+static gboolean
+merged_contains_file (CajaDirectory *directory,
+ CajaFile *file)
+{
+ CajaMergedDirectory *merged;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ if (caja_directory_contains_file (node->data, file))
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* Return true only if all directories in the list do. */
+static gboolean
+merged_are_all_files_seen (CajaDirectory *directory)
+{
+ CajaMergedDirectory *merged;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ if (!caja_directory_are_all_files_seen (node->data))
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+/* Return true if any directory in the list does. */
+static gboolean
+merged_is_not_empty (CajaDirectory *directory)
+{
+ CajaMergedDirectory *merged;
+ GList *node;
+
+ merged = CAJA_MERGED_DIRECTORY (directory);
+
+ for (node = merged->details->directories; node != NULL; node = node->next)
+ {
+ if (caja_directory_is_not_empty (node->data))
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static GList *
+merged_get_file_list (CajaDirectory *directory)
+{
+ GList *dirs_file_list, *merged_dir_file_list = NULL;
+ GList *dir_list;
+ GList *cur_node;
+
+ dirs_file_list = NULL;
+ dir_list = CAJA_MERGED_DIRECTORY (directory)->details->directories;
+
+ for (cur_node = dir_list; cur_node != NULL; cur_node = cur_node->next)
+ {
+ CajaDirectory *cur_dir;
+
+ cur_dir = CAJA_DIRECTORY (cur_node->data);
+ dirs_file_list = g_list_concat (dirs_file_list,
+ caja_directory_get_file_list (cur_dir));
+ }
+
+ merged_dir_file_list = EEL_CALL_PARENT_WITH_RETURN_VALUE
+ (CAJA_DIRECTORY_CLASS, get_file_list, (directory));
+
+ return g_list_concat (dirs_file_list, merged_dir_file_list);
+}
+
+static void
+forward_files_added_cover (CajaDirectory *real_directory,
+ GList *files,
+ gpointer callback_data)
+{
+ caja_directory_emit_files_added (CAJA_DIRECTORY (callback_data), files);
+}
+
+static void
+forward_files_changed_cover (CajaDirectory *real_directory,
+ GList *files,
+ gpointer callback_data)
+{
+ caja_directory_emit_files_changed (CAJA_DIRECTORY (callback_data), files);
+}
+
+static void
+done_loading_callback (CajaDirectory *real_directory,
+ CajaMergedDirectory *merged)
+{
+ merged->details->directories_not_done_loading = g_list_remove
+ (merged->details->directories_not_done_loading, real_directory);
+ if (merged->details->directories_not_done_loading == NULL)
+ {
+ caja_directory_emit_done_loading (CAJA_DIRECTORY (merged));
+ }
+}
+
+static void
+monitor_add_directory (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ MergedMonitor *monitor;
+
+ monitor = value;
+ caja_directory_file_monitor_add
+ (CAJA_DIRECTORY (callback_data), monitor,
+ monitor->monitor_hidden_files,
+ monitor->monitor_backup_files,
+ monitor->monitor_attributes,
+ forward_files_added_cover, monitor->merged);
+}
+
+static void
+merged_add_real_directory (CajaMergedDirectory *merged,
+ CajaDirectory *real_directory)
+{
+ g_return_if_fail (CAJA_IS_MERGED_DIRECTORY (merged));
+ g_return_if_fail (CAJA_IS_DIRECTORY (real_directory));
+ g_return_if_fail (!CAJA_IS_MERGED_DIRECTORY (real_directory));
+ g_return_if_fail (g_list_find (merged->details->directories, real_directory) == NULL);
+
+ /* Add to our list of directories. */
+ caja_directory_ref (real_directory);
+ merged->details->directories = g_list_prepend
+ (merged->details->directories, real_directory);
+ merged->details->directories_not_done_loading = g_list_prepend
+ (merged->details->directories_not_done_loading, real_directory);
+
+ g_signal_connect_object (real_directory, "done_loading",
+ G_CALLBACK (done_loading_callback), merged, 0);
+
+ /* FIXME bugzilla.gnome.org 45084: The done_loading part won't work for the case where
+ * we have no directories in our list.
+ */
+
+ /* Add the directory to any extant monitors. */
+ g_hash_table_foreach (merged->details->monitors,
+ monitor_add_directory,
+ real_directory);
+ /* FIXME bugzilla.gnome.org 42541: Do we need to add the directory to callbacks too? */
+
+ g_signal_connect_object (real_directory, "files_added",
+ G_CALLBACK (forward_files_added_cover), merged, 0);
+ g_signal_connect_object (real_directory, "files_changed",
+ G_CALLBACK (forward_files_changed_cover), merged, 0);
+}
+
+void
+caja_merged_directory_add_real_directory (CajaMergedDirectory *merged,
+ CajaDirectory *real_directory)
+{
+ g_return_if_fail (CAJA_IS_MERGED_DIRECTORY (merged));
+ g_return_if_fail (CAJA_IS_DIRECTORY (real_directory));
+ g_return_if_fail (!CAJA_IS_MERGED_DIRECTORY (real_directory));
+
+ /* Quietly do nothing if asked to add something that's already there. */
+ if (g_list_find (merged->details->directories, real_directory) != NULL)
+ {
+ return;
+ }
+
+ g_signal_emit (merged, signals[ADD_REAL_DIRECTORY], 0, real_directory);
+}
+
+GList *
+caja_merged_directory_get_real_directories (CajaMergedDirectory *merged)
+{
+ return g_list_copy (merged->details->directories);
+}
+
+static void
+merged_callback_remove_directory_cover (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ merged_callback_remove_directory
+ (value, CAJA_DIRECTORY (callback_data));
+}
+
+static void
+monitor_remove_directory (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ caja_directory_file_monitor_remove
+ (CAJA_DIRECTORY (callback_data), value);
+}
+
+static void
+real_directory_notify_files_removed (CajaDirectory *real_directory)
+{
+ GList *files, *l;
+
+ files = caja_directory_get_file_list (real_directory);
+
+ for (l = files; l; l = l->next)
+ {
+ CajaFile *file;
+ char *uri;
+
+ file = CAJA_FILE (l->data);
+ uri = caja_file_get_uri (file);
+ caja_file_unref (file);
+
+ l->data = uri;
+ }
+
+ if (files)
+ {
+ caja_directory_notify_files_removed_by_uri (files);
+ }
+
+ eel_g_list_free_deep (files);
+}
+
+static void
+merged_remove_real_directory (CajaMergedDirectory *merged,
+ CajaDirectory *real_directory)
+{
+ g_return_if_fail (CAJA_IS_MERGED_DIRECTORY (merged));
+ g_return_if_fail (CAJA_IS_DIRECTORY (real_directory));
+ g_return_if_fail (g_list_find (merged->details->directories, real_directory) != NULL);
+
+ /* Since the real directory will be going away, act as if files were removed */
+ real_directory_notify_files_removed (real_directory);
+
+ /* Remove this directory from callbacks and monitors. */
+ eel_g_hash_table_safe_for_each (merged->details->callbacks,
+ merged_callback_remove_directory_cover,
+ real_directory);
+ g_hash_table_foreach (merged->details->monitors,
+ monitor_remove_directory,
+ real_directory);
+
+ /* Disconnect all the signals. */
+ g_signal_handlers_disconnect_matched
+ (real_directory, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, merged);
+
+ /* Remove from our list of directories. */
+ merged->details->directories = g_list_remove
+ (merged->details->directories, real_directory);
+ merged->details->directories_not_done_loading = g_list_remove
+ (merged->details->directories_not_done_loading, real_directory);
+ caja_directory_unref (real_directory);
+}
+
+void
+caja_merged_directory_remove_real_directory (CajaMergedDirectory *merged,
+ CajaDirectory *real_directory)
+{
+ g_return_if_fail (CAJA_IS_MERGED_DIRECTORY (merged));
+
+ /* Quietly do nothing if asked to remove something that's not there. */
+ if (g_list_find (merged->details->directories, real_directory) == NULL)
+ {
+ return;
+ }
+
+ g_signal_emit (merged, signals[REMOVE_REAL_DIRECTORY], 0, real_directory);
+}
+
+static void
+merged_monitor_destroy_cover (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ merged_monitor_destroy (callback_data, value);
+}
+
+static void
+merged_callback_destroy_cover (gpointer key,
+ gpointer value,
+ gpointer callback_data)
+{
+ merged_callback_destroy (value);
+}
+
+static void
+merged_finalize (GObject *object)
+{
+ CajaMergedDirectory *merged;
+
+ merged = CAJA_MERGED_DIRECTORY (object);
+
+ g_hash_table_foreach (merged->details->monitors,
+ merged_monitor_destroy_cover, merged);
+ g_hash_table_foreach (merged->details->callbacks,
+ merged_callback_destroy_cover, NULL);
+
+ g_hash_table_destroy (merged->details->callbacks);
+ g_hash_table_destroy (merged->details->monitors);
+ caja_directory_list_free (merged->details->directories);
+ g_list_free (merged->details->directories_not_done_loading);
+ g_free (merged->details);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+caja_merged_directory_init (CajaMergedDirectory *merged)
+{
+ merged->details = g_new0 (CajaMergedDirectoryDetails, 1);
+ merged->details->callbacks = g_hash_table_new
+ (merged_callback_hash, merged_callback_equal);
+ merged->details->monitors = g_hash_table_new (NULL, NULL);
+}
+
+static void
+caja_merged_directory_class_init (CajaMergedDirectoryClass *class)
+{
+ CajaDirectoryClass *directory_class;
+
+ directory_class = CAJA_DIRECTORY_CLASS (class);
+
+ G_OBJECT_CLASS (class)->finalize = merged_finalize;
+
+ directory_class->contains_file = merged_contains_file;
+ directory_class->call_when_ready = merged_call_when_ready;
+ directory_class->cancel_callback = merged_cancel_callback;
+ directory_class->file_monitor_add = merged_monitor_add;
+ directory_class->file_monitor_remove = merged_monitor_remove;
+ directory_class->force_reload = merged_force_reload;
+ directory_class->are_all_files_seen = merged_are_all_files_seen;
+ directory_class->is_not_empty = merged_is_not_empty;
+ /* Override get_file_list so that we can return a list that includes
+ * the files from each of the directories in CajaMergedDirectory->details->directories.
+ */
+ directory_class->get_file_list = merged_get_file_list;
+
+ class->add_real_directory = merged_add_real_directory;
+ class->remove_real_directory = merged_remove_real_directory;
+
+ signals[ADD_REAL_DIRECTORY]
+ = g_signal_new ("add_real_directory",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaMergedDirectoryClass,
+ add_real_directory),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+ signals[REMOVE_REAL_DIRECTORY]
+ = g_signal_new ("remove_real_directory",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CajaMergedDirectoryClass,
+ remove_real_directory),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+}