Updated Slovenian translation
[nautilus.git] / libnautilus-private / nautilus-merged-directory.c
blob8b4667b99b30980e4f678226f5bbc04ea19e4b27
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>
26 #include <config.h>
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 {
37 GList *directories;
38 GList *directories_not_done_loading;
39 GHashTable *callbacks;
40 GHashTable *monitors;
43 typedef struct {
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;
53 } MergedCallback;
55 typedef struct {
56 NautilusMergedDirectory *merged;
58 gboolean monitor_hidden_files;
59 gboolean monitor_backup_files;
60 NautilusFileAttributes monitor_attributes;
61 } MergedMonitor;
63 enum {
64 ADD_REAL_DIRECTORY,
65 REMOVE_REAL_DIRECTORY,
66 LAST_SIGNAL
69 static guint signals[LAST_SIGNAL];
71 GNOME_CLASS_BOILERPLATE (NautilusMergedDirectory, nautilus_merged_directory,
72 NautilusDirectory, NAUTILUS_TYPE_DIRECTORY)
74 static guint
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);
84 static gboolean
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;
97 static void
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);
108 static void
109 merged_callback_check_done (MergedCallback *merged_callback)
111 /* Check if we are ready. */
112 if (merged_callback->non_ready_directories != NULL) {
113 return;
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);
128 static void
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);
137 static void
138 directory_ready_callback (NautilusDirectory *directory,
139 GList *files,
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);
159 static void
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;
168 GList *node;
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");
177 return;
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
204 (node->data,
205 merged_callback->wait_for_attributes,
206 merged_callback->wait_for_file_list,
207 directory_ready_callback, merged_callback);
211 static void
212 merged_cancel_callback (NautilusDirectory *directory,
213 NautilusDirectoryCallback callback,
214 gpointer callback_data)
216 NautilusMergedDirectory *merged;
217 MergedCallback search_key, *merged_callback;
218 GList *node;
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) {
227 return;
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
236 (node->data,
237 directory_ready_callback, merged_callback);
239 merged_callback_destroy (merged_callback);
242 static void
243 build_merged_callback_list (NautilusDirectory *directory,
244 GList *file_list,
245 gpointer callback_data)
247 GList **merged_list;
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. */
255 static void
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;
266 GList *node;
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);
277 } else {
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,
293 file_attributes,
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);
302 static void
303 merged_monitor_destroy (NautilusMergedDirectory *merged, MergedMonitor *monitor)
305 GList *node;
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);
312 g_free (monitor);
315 /* Remove the monitor from each of the directories in the list. */
316 static void
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) {
328 return;
330 g_hash_table_remove (merged->details->monitors, client);
332 merged_monitor_destroy (merged, monitor);
335 static void
336 merged_force_reload (NautilusDirectory *directory)
338 NautilusMergedDirectory *merged;
339 GList *node;
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. */
350 static gboolean
351 merged_contains_file (NautilusDirectory *directory,
352 NautilusFile *file)
354 NautilusMergedDirectory *merged;
355 GList *node;
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)) {
361 return TRUE;
364 return FALSE;
367 /* Return true only if all directories in the list do. */
368 static gboolean
369 merged_are_all_files_seen (NautilusDirectory *directory)
371 NautilusMergedDirectory *merged;
372 GList *node;
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)) {
378 return FALSE;
381 return TRUE;
384 /* Return true if any directory in the list does. */
385 static gboolean
386 merged_is_not_empty (NautilusDirectory *directory)
388 NautilusMergedDirectory *merged;
389 GList *node;
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)) {
395 return TRUE;
398 return FALSE;
401 static GList *
402 merged_get_file_list (NautilusDirectory *directory)
404 GList *dirs_file_list, *merged_dir_file_list;
405 GList *dir_list;
406 GList *cur_node;
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);
425 static void
426 forward_files_added_cover (NautilusDirectory *real_directory,
427 GList *files,
428 gpointer callback_data)
430 nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (callback_data), files);
433 static void
434 forward_files_changed_cover (NautilusDirectory *real_directory,
435 GList *files,
436 gpointer callback_data)
438 nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (callback_data), files);
441 static void
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));
452 static void
453 monitor_add_directory (gpointer key,
454 gpointer value,
455 gpointer callback_data)
457 MergedMonitor *monitor;
459 monitor = value;
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);
468 static void
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,
494 real_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);
503 void
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) {
513 return;
516 g_signal_emit (merged, signals[ADD_REAL_DIRECTORY], 0, real_directory);
519 GList *
520 nautilus_merged_directory_get_real_directories (NautilusMergedDirectory *merged)
522 return g_list_copy (merged->details->directories);
525 static void
526 merged_callback_remove_directory_cover (gpointer key,
527 gpointer value,
528 gpointer callback_data)
530 merged_callback_remove_directory
531 (value, NAUTILUS_DIRECTORY (callback_data));
534 static void
535 monitor_remove_directory (gpointer key,
536 gpointer value,
537 gpointer callback_data)
539 nautilus_directory_file_monitor_remove
540 (NAUTILUS_DIRECTORY (callback_data), value);
543 static void
544 real_directory_notify_files_removed (NautilusDirectory *real_directory)
546 GList *files, *l;
548 files = nautilus_directory_get_file_list (real_directory);
550 for (l = files; l; l = l->next) {
551 NautilusFile *file;
552 char *uri;
554 file = NAUTILUS_FILE (l->data);
555 uri = nautilus_file_get_uri (file);
556 nautilus_file_unref (file);
558 l->data = uri;
561 if (files) {
562 nautilus_directory_notify_files_removed_by_uri (files);
565 eel_g_list_free_deep (files);
568 static void
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,
582 real_directory);
583 g_hash_table_foreach (merged->details->monitors,
584 monitor_remove_directory,
585 real_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);
600 void
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) {
608 return;
611 g_signal_emit (merged, signals[REMOVE_REAL_DIRECTORY], 0, real_directory);
614 static void
615 merged_monitor_destroy_cover (gpointer key,
616 gpointer value,
617 gpointer callback_data)
619 merged_monitor_destroy (callback_data, value);
622 static void
623 merged_callback_destroy_cover (gpointer key,
624 gpointer value,
625 gpointer callback_data)
627 merged_callback_destroy (value);
630 static void
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);
651 static void
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);
660 static void
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),
688 G_SIGNAL_RUN_LAST,
689 G_STRUCT_OFFSET (NautilusMergedDirectoryClass,
690 add_real_directory),
691 NULL, NULL,
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),
697 G_SIGNAL_RUN_LAST,
698 G_STRUCT_OFFSET (NautilusMergedDirectoryClass,
699 remove_real_directory),
700 NULL, NULL,
701 g_cclosure_marshal_VOID__POINTER,
702 G_TYPE_NONE, 1, G_TYPE_POINTER);