1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
3 nautilus-merged-directory.c: Subclass of NautilusDirectory to implement the
4 virtual merged directory.
6 Copyright (C) 1999, 2000 Eazel, Inc.
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public
19 License along with this program; if not, write to the
20 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 Boston, MA 02111-1307, USA.
23 Author: Darin Adler <darin@bentspoon.com>
27 #include "nautilus-merged-directory.h"
29 #include "nautilus-directory-private.h"
30 #include "nautilus-directory-notify.h"
31 #include "nautilus-file.h"
32 #include <eel/eel-glib-extensions.h>
33 #include <gtk/gtksignal.h>
34 #include <libgnome/gnome-macros.h>
36 struct NautilusMergedDirectoryDetails
{
38 GList
*directories_not_done_loading
;
39 GHashTable
*callbacks
;
44 NautilusMergedDirectory
*merged
;
45 NautilusDirectoryCallback callback
;
46 gpointer callback_data
;
48 NautilusFileAttributes wait_for_attributes
;
49 gboolean wait_for_file_list
;
51 GList
*non_ready_directories
;
52 GList
*merged_file_list
;
56 NautilusMergedDirectory
*merged
;
58 gboolean monitor_hidden_files
;
59 gboolean monitor_backup_files
;
60 NautilusFileAttributes monitor_attributes
;
65 REMOVE_REAL_DIRECTORY
,
69 static guint signals
[LAST_SIGNAL
];
71 GNOME_CLASS_BOILERPLATE (NautilusMergedDirectory
, nautilus_merged_directory
,
72 NautilusDirectory
, NAUTILUS_TYPE_DIRECTORY
)
75 merged_callback_hash (gconstpointer merged_callback_as_pointer
)
77 const MergedCallback
*merged_callback
;
79 merged_callback
= merged_callback_as_pointer
;
80 return GPOINTER_TO_UINT (merged_callback
->callback
)
81 ^ GPOINTER_TO_UINT (merged_callback
->callback_data
);
85 merged_callback_equal (gconstpointer merged_callback_as_pointer
,
86 gconstpointer merged_callback_as_pointer_2
)
88 const MergedCallback
*merged_callback
, *merged_callback_2
;
90 merged_callback
= merged_callback_as_pointer
;
91 merged_callback_2
= merged_callback_as_pointer_2
;
93 return merged_callback
->callback
== merged_callback_2
->callback
94 && merged_callback
->callback_data
== merged_callback_2
->callback_data
;
98 merged_callback_destroy (MergedCallback
*merged_callback
)
100 g_assert (merged_callback
!= NULL
);
101 g_assert (NAUTILUS_IS_MERGED_DIRECTORY (merged_callback
->merged
));
103 g_list_free (merged_callback
->non_ready_directories
);
104 nautilus_file_list_free (merged_callback
->merged_file_list
);
105 g_free (merged_callback
);
109 merged_callback_check_done (MergedCallback
*merged_callback
)
111 /* Check if we are ready. */
112 if (merged_callback
->non_ready_directories
!= NULL
) {
116 /* Remove from the hash table before sending it. */
117 g_hash_table_remove (merged_callback
->merged
->details
->callbacks
, merged_callback
);
119 /* We are ready, so do the real callback. */
120 (* merged_callback
->callback
) (NAUTILUS_DIRECTORY (merged_callback
->merged
),
121 merged_callback
->merged_file_list
,
122 merged_callback
->callback_data
);
124 /* And we are done. */
125 merged_callback_destroy (merged_callback
);
129 merged_callback_remove_directory (MergedCallback
*merged_callback
,
130 NautilusDirectory
*directory
)
132 merged_callback
->non_ready_directories
= g_list_remove
133 (merged_callback
->non_ready_directories
, directory
);
134 merged_callback_check_done (merged_callback
);
138 directory_ready_callback (NautilusDirectory
*directory
,
140 gpointer callback_data
)
142 MergedCallback
*merged_callback
;
144 g_assert (NAUTILUS_IS_DIRECTORY (directory
));
145 g_assert (callback_data
!= NULL
);
147 merged_callback
= callback_data
;
148 g_assert (g_list_find (merged_callback
->non_ready_directories
, directory
) != NULL
);
150 /* Update based on this call. */
151 merged_callback
->merged_file_list
= g_list_concat
152 (merged_callback
->merged_file_list
,
153 nautilus_file_list_copy (files
));
155 /* Check if we are ready. */
156 merged_callback_remove_directory (merged_callback
, directory
);
160 merged_call_when_ready (NautilusDirectory
*directory
,
161 NautilusFileAttributes file_attributes
,
162 gboolean wait_for_file_list
,
163 NautilusDirectoryCallback callback
,
164 gpointer callback_data
)
166 NautilusMergedDirectory
*merged
;
167 MergedCallback search_key
, *merged_callback
;
170 merged
= NAUTILUS_MERGED_DIRECTORY (directory
);
172 /* Check to be sure we aren't overwriting. */
173 search_key
.callback
= callback
;
174 search_key
.callback_data
= callback_data
;
175 if (g_hash_table_lookup (merged
->details
->callbacks
, &search_key
) != NULL
) {
176 g_warning ("tried to add a new callback while an old one was pending");
180 /* Create a merged_callback record. */
181 merged_callback
= g_new0 (MergedCallback
, 1);
182 merged_callback
->merged
= merged
;
183 merged_callback
->callback
= callback
;
184 merged_callback
->callback_data
= callback_data
;
185 merged_callback
->wait_for_attributes
= file_attributes
;
186 merged_callback
->wait_for_file_list
= wait_for_file_list
;
187 for (node
= merged
->details
->directories
; node
!= NULL
; node
= node
->next
) {
188 merged_callback
->non_ready_directories
= g_list_prepend
189 (merged_callback
->non_ready_directories
, node
->data
);
192 /* Put it in the hash table. */
193 g_hash_table_insert (merged
->details
->callbacks
,
194 merged_callback
, merged_callback
);
196 /* Handle the pathological case where there are no directories. */
197 if (merged
->details
->directories
== NULL
) {
198 merged_callback_check_done (merged_callback
);
201 /* Now tell all the directories about it. */
202 for (node
= merged
->details
->directories
; node
!= NULL
; node
= node
->next
) {
203 nautilus_directory_call_when_ready
205 merged_callback
->wait_for_attributes
,
206 merged_callback
->wait_for_file_list
,
207 directory_ready_callback
, merged_callback
);
212 merged_cancel_callback (NautilusDirectory
*directory
,
213 NautilusDirectoryCallback callback
,
214 gpointer callback_data
)
216 NautilusMergedDirectory
*merged
;
217 MergedCallback search_key
, *merged_callback
;
220 merged
= NAUTILUS_MERGED_DIRECTORY (directory
);
222 /* Find the entry in the table. */
223 search_key
.callback
= callback
;
224 search_key
.callback_data
= callback_data
;
225 merged_callback
= g_hash_table_lookup (merged
->details
->callbacks
, &search_key
);
226 if (merged_callback
== NULL
) {
230 /* Remove from the hash table before working with it. */
231 g_hash_table_remove (merged_callback
->merged
->details
->callbacks
, merged_callback
);
233 /* Tell all the directories to cancel the call. */
234 for (node
= merged_callback
->non_ready_directories
; node
!= NULL
; node
= node
->next
) {
235 nautilus_directory_cancel_callback
237 directory_ready_callback
, merged_callback
);
239 merged_callback_destroy (merged_callback
);
243 build_merged_callback_list (NautilusDirectory
*directory
,
245 gpointer callback_data
)
249 merged_list
= callback_data
;
250 *merged_list
= g_list_concat (*merged_list
,
251 nautilus_file_list_copy (file_list
));
254 /* Create a monitor on each of the directories in the list. */
256 merged_monitor_add (NautilusDirectory
*directory
,
257 gconstpointer client
,
258 gboolean monitor_hidden_files
,
259 gboolean monitor_backup_files
,
260 NautilusFileAttributes file_attributes
,
261 NautilusDirectoryCallback callback
,
262 gpointer callback_data
)
264 NautilusMergedDirectory
*merged
;
265 MergedMonitor
*monitor
;
267 GList
*merged_callback_list
;
269 merged
= NAUTILUS_MERGED_DIRECTORY (directory
);
271 /* Map the client to a unique value so this doesn't interfere
272 * with direct monitoring of the directory by the same client.
274 monitor
= g_hash_table_lookup (merged
->details
->monitors
, client
);
275 if (monitor
!= NULL
) {
276 g_assert (monitor
->merged
== merged
);
278 monitor
= g_new0 (MergedMonitor
, 1);
279 monitor
->merged
= merged
;
280 g_hash_table_insert (merged
->details
->monitors
,
281 (gpointer
) client
, monitor
);
283 monitor
->monitor_hidden_files
= monitor_hidden_files
;
284 monitor
->monitor_backup_files
= monitor_backup_files
;
285 monitor
->monitor_attributes
= file_attributes
;
287 /* Call through to the real directory add calls. */
288 merged_callback_list
= NULL
;
289 for (node
= merged
->details
->directories
; node
!= NULL
; node
= node
->next
) {
290 nautilus_directory_file_monitor_add
291 (node
->data
, monitor
,
292 monitor_hidden_files
, monitor_backup_files
,
294 build_merged_callback_list
, &merged_callback_list
);
296 if (callback
!= NULL
) {
297 (* callback
) (directory
, merged_callback_list
, callback_data
);
299 nautilus_file_list_free (merged_callback_list
);
303 merged_monitor_destroy (NautilusMergedDirectory
*merged
, MergedMonitor
*monitor
)
307 /* Call through to the real directory remove calls. */
308 for (node
= merged
->details
->directories
; node
!= NULL
; node
= node
->next
) {
309 nautilus_directory_file_monitor_remove (node
->data
, monitor
);
315 /* Remove the monitor from each of the directories in the list. */
317 merged_monitor_remove (NautilusDirectory
*directory
,
318 gconstpointer client
)
320 NautilusMergedDirectory
*merged
;
321 MergedMonitor
*monitor
;
323 merged
= NAUTILUS_MERGED_DIRECTORY (directory
);
325 /* Map the client to the value used by the earlier add call. */
326 monitor
= g_hash_table_lookup (merged
->details
->monitors
, client
);
327 if (monitor
== NULL
) {
330 g_hash_table_remove (merged
->details
->monitors
, client
);
332 merged_monitor_destroy (merged
, monitor
);
336 merged_force_reload (NautilusDirectory
*directory
)
338 NautilusMergedDirectory
*merged
;
341 merged
= NAUTILUS_MERGED_DIRECTORY (directory
);
343 /* Call through to the real force_reload calls. */
344 for (node
= merged
->details
->directories
; node
!= NULL
; node
= node
->next
) {
345 nautilus_directory_force_reload (node
->data
);
349 /* Return true if any directory in the list does. */
351 merged_contains_file (NautilusDirectory
*directory
,
354 NautilusMergedDirectory
*merged
;
357 merged
= NAUTILUS_MERGED_DIRECTORY (directory
);
359 for (node
= merged
->details
->directories
; node
!= NULL
; node
= node
->next
) {
360 if (nautilus_directory_contains_file (node
->data
, file
)) {
367 /* Return true only if all directories in the list do. */
369 merged_are_all_files_seen (NautilusDirectory
*directory
)
371 NautilusMergedDirectory
*merged
;
374 merged
= NAUTILUS_MERGED_DIRECTORY (directory
);
376 for (node
= merged
->details
->directories
; node
!= NULL
; node
= node
->next
) {
377 if (!nautilus_directory_are_all_files_seen (node
->data
)) {
384 /* Return true if any directory in the list does. */
386 merged_is_not_empty (NautilusDirectory
*directory
)
388 NautilusMergedDirectory
*merged
;
391 merged
= NAUTILUS_MERGED_DIRECTORY (directory
);
393 for (node
= merged
->details
->directories
; node
!= NULL
; node
= node
->next
) {
394 if (nautilus_directory_is_not_empty (node
->data
)) {
402 merged_get_file_list (NautilusDirectory
*directory
)
404 GList
*dirs_file_list
, *merged_dir_file_list
;
408 dirs_file_list
= NULL
;
409 dir_list
= NAUTILUS_MERGED_DIRECTORY (directory
)->details
->directories
;
411 for (cur_node
= dir_list
; cur_node
!= NULL
; cur_node
= cur_node
->next
) {
412 NautilusDirectory
*cur_dir
;
414 cur_dir
= NAUTILUS_DIRECTORY (cur_node
->data
);
415 dirs_file_list
= g_list_concat (dirs_file_list
,
416 nautilus_directory_get_file_list (cur_dir
));
419 merged_dir_file_list
= GNOME_CALL_PARENT_WITH_DEFAULT
420 (NAUTILUS_DIRECTORY_CLASS
, get_file_list
, (directory
), NULL
);
422 return g_list_concat (dirs_file_list
, merged_dir_file_list
);
426 forward_files_added_cover (NautilusDirectory
*real_directory
,
428 gpointer callback_data
)
430 nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (callback_data
), files
);
434 forward_files_changed_cover (NautilusDirectory
*real_directory
,
436 gpointer callback_data
)
438 nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (callback_data
), files
);
442 done_loading_callback (NautilusDirectory
*real_directory
,
443 NautilusMergedDirectory
*merged
)
445 merged
->details
->directories_not_done_loading
= g_list_remove
446 (merged
->details
->directories_not_done_loading
, real_directory
);
447 if (merged
->details
->directories_not_done_loading
== NULL
) {
448 nautilus_directory_emit_done_loading (NAUTILUS_DIRECTORY (merged
));
453 monitor_add_directory (gpointer key
,
455 gpointer callback_data
)
457 MergedMonitor
*monitor
;
460 nautilus_directory_file_monitor_add
461 (NAUTILUS_DIRECTORY (callback_data
), monitor
,
462 monitor
->monitor_hidden_files
,
463 monitor
->monitor_backup_files
,
464 monitor
->monitor_attributes
,
465 forward_files_added_cover
, monitor
->merged
);
469 merged_add_real_directory (NautilusMergedDirectory
*merged
,
470 NautilusDirectory
*real_directory
)
472 g_return_if_fail (NAUTILUS_IS_MERGED_DIRECTORY (merged
));
473 g_return_if_fail (NAUTILUS_IS_DIRECTORY (real_directory
));
474 g_return_if_fail (!NAUTILUS_IS_MERGED_DIRECTORY (real_directory
));
475 g_return_if_fail (g_list_find (merged
->details
->directories
, real_directory
) == NULL
);
477 /* Add to our list of directories. */
478 nautilus_directory_ref (real_directory
);
479 merged
->details
->directories
= g_list_prepend
480 (merged
->details
->directories
, real_directory
);
481 merged
->details
->directories_not_done_loading
= g_list_prepend
482 (merged
->details
->directories_not_done_loading
, real_directory
);
484 g_signal_connect_object (real_directory
, "done_loading",
485 G_CALLBACK (done_loading_callback
), merged
, 0);
487 /* FIXME bugzilla.gnome.org 45084: The done_loading part won't work for the case where
488 * we have no directories in our list.
491 /* Add the directory to any extant monitors. */
492 g_hash_table_foreach (merged
->details
->monitors
,
493 monitor_add_directory
,
495 /* FIXME bugzilla.gnome.org 42541: Do we need to add the directory to callbacks too? */
497 g_signal_connect_object (real_directory
, "files_added",
498 G_CALLBACK (forward_files_added_cover
), merged
, 0);
499 g_signal_connect_object (real_directory
, "files_changed",
500 G_CALLBACK (forward_files_changed_cover
), merged
, 0);
504 nautilus_merged_directory_add_real_directory (NautilusMergedDirectory
*merged
,
505 NautilusDirectory
*real_directory
)
507 g_return_if_fail (NAUTILUS_IS_MERGED_DIRECTORY (merged
));
508 g_return_if_fail (NAUTILUS_IS_DIRECTORY (real_directory
));
509 g_return_if_fail (!NAUTILUS_IS_MERGED_DIRECTORY (real_directory
));
511 /* Quietly do nothing if asked to add something that's already there. */
512 if (g_list_find (merged
->details
->directories
, real_directory
) != NULL
) {
516 g_signal_emit (merged
, signals
[ADD_REAL_DIRECTORY
], 0, real_directory
);
520 nautilus_merged_directory_get_real_directories (NautilusMergedDirectory
*merged
)
522 return g_list_copy (merged
->details
->directories
);
526 merged_callback_remove_directory_cover (gpointer key
,
528 gpointer callback_data
)
530 merged_callback_remove_directory
531 (value
, NAUTILUS_DIRECTORY (callback_data
));
535 monitor_remove_directory (gpointer key
,
537 gpointer callback_data
)
539 nautilus_directory_file_monitor_remove
540 (NAUTILUS_DIRECTORY (callback_data
), value
);
544 real_directory_notify_files_removed (NautilusDirectory
*real_directory
)
548 files
= nautilus_directory_get_file_list (real_directory
);
550 for (l
= files
; l
; l
= l
->next
) {
554 file
= NAUTILUS_FILE (l
->data
);
555 uri
= nautilus_file_get_uri (file
);
556 nautilus_file_unref (file
);
562 nautilus_directory_notify_files_removed_by_uri (files
);
565 eel_g_list_free_deep (files
);
569 merged_remove_real_directory (NautilusMergedDirectory
*merged
,
570 NautilusDirectory
*real_directory
)
572 g_return_if_fail (NAUTILUS_IS_MERGED_DIRECTORY (merged
));
573 g_return_if_fail (NAUTILUS_IS_DIRECTORY (real_directory
));
574 g_return_if_fail (g_list_find (merged
->details
->directories
, real_directory
) != NULL
);
576 /* Since the real directory will be going away, act as if files were removed */
577 real_directory_notify_files_removed (real_directory
);
579 /* Remove this directory from callbacks and monitors. */
580 eel_g_hash_table_safe_for_each (merged
->details
->callbacks
,
581 merged_callback_remove_directory_cover
,
583 g_hash_table_foreach (merged
->details
->monitors
,
584 monitor_remove_directory
,
587 /* Disconnect all the signals. */
588 g_signal_handlers_disconnect_matched
589 (real_directory
, G_SIGNAL_MATCH_DATA
,
590 0, 0, NULL
, NULL
, merged
);
592 /* Remove from our list of directories. */
593 merged
->details
->directories
= g_list_remove
594 (merged
->details
->directories
, real_directory
);
595 merged
->details
->directories_not_done_loading
= g_list_remove
596 (merged
->details
->directories_not_done_loading
, real_directory
);
597 nautilus_directory_unref (real_directory
);
601 nautilus_merged_directory_remove_real_directory (NautilusMergedDirectory
*merged
,
602 NautilusDirectory
*real_directory
)
604 g_return_if_fail (NAUTILUS_IS_MERGED_DIRECTORY (merged
));
606 /* Quietly do nothing if asked to remove something that's not there. */
607 if (g_list_find (merged
->details
->directories
, real_directory
) == NULL
) {
611 g_signal_emit (merged
, signals
[REMOVE_REAL_DIRECTORY
], 0, real_directory
);
615 merged_monitor_destroy_cover (gpointer key
,
617 gpointer callback_data
)
619 merged_monitor_destroy (callback_data
, value
);
623 merged_callback_destroy_cover (gpointer key
,
625 gpointer callback_data
)
627 merged_callback_destroy (value
);
631 merged_finalize (GObject
*object
)
633 NautilusMergedDirectory
*merged
;
635 merged
= NAUTILUS_MERGED_DIRECTORY (object
);
637 g_hash_table_foreach (merged
->details
->monitors
,
638 merged_monitor_destroy_cover
, merged
);
639 g_hash_table_foreach (merged
->details
->callbacks
,
640 merged_callback_destroy_cover
, NULL
);
642 g_hash_table_destroy (merged
->details
->callbacks
);
643 g_hash_table_destroy (merged
->details
->monitors
);
644 nautilus_directory_list_free (merged
->details
->directories
);
645 g_list_free (merged
->details
->directories_not_done_loading
);
646 g_free (merged
->details
);
648 G_OBJECT_CLASS (parent_class
)->finalize (object
);
652 nautilus_merged_directory_instance_init (NautilusMergedDirectory
*merged
)
654 merged
->details
= g_new0 (NautilusMergedDirectoryDetails
, 1);
655 merged
->details
->callbacks
= g_hash_table_new
656 (merged_callback_hash
, merged_callback_equal
);
657 merged
->details
->monitors
= g_hash_table_new (NULL
, NULL
);
661 nautilus_merged_directory_class_init (NautilusMergedDirectoryClass
*class)
663 NautilusDirectoryClass
*directory_class
;
665 directory_class
= NAUTILUS_DIRECTORY_CLASS (class);
667 G_OBJECT_CLASS (class)->finalize
= merged_finalize
;
669 directory_class
->contains_file
= merged_contains_file
;
670 directory_class
->call_when_ready
= merged_call_when_ready
;
671 directory_class
->cancel_callback
= merged_cancel_callback
;
672 directory_class
->file_monitor_add
= merged_monitor_add
;
673 directory_class
->file_monitor_remove
= merged_monitor_remove
;
674 directory_class
->force_reload
= merged_force_reload
;
675 directory_class
->are_all_files_seen
= merged_are_all_files_seen
;
676 directory_class
->is_not_empty
= merged_is_not_empty
;
677 /* Override get_file_list so that we can return a list that includes
678 * the files from each of the directories in NautilusMergedDirectory->details->directories.
680 directory_class
->get_file_list
= merged_get_file_list
;
682 class->add_real_directory
= merged_add_real_directory
;
683 class->remove_real_directory
= merged_remove_real_directory
;
685 signals
[ADD_REAL_DIRECTORY
]
686 = g_signal_new ("add_real_directory",
687 G_TYPE_FROM_CLASS (class),
689 G_STRUCT_OFFSET (NautilusMergedDirectoryClass
,
692 g_cclosure_marshal_VOID__POINTER
,
693 G_TYPE_NONE
, 1, G_TYPE_POINTER
);
694 signals
[REMOVE_REAL_DIRECTORY
]
695 = g_signal_new ("remove_real_directory",
696 G_TYPE_FROM_CLASS (class),
698 G_STRUCT_OFFSET (NautilusMergedDirectoryClass
,
699 remove_real_directory
),
701 g_cclosure_marshal_VOID__POINTER
,
702 G_TYPE_NONE
, 1, G_TYPE_POINTER
);