No issues found
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2
3 nautilus-desktop-directory.c: Subclass of NautilusDirectory to implement
4 a virtual directory consisting of the desktop directory and the desktop
5 icons
6
7 Copyright (C) 2003 Red Hat, Inc.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
18
19 You should have received a copy of the GNU General Public
20 License along with this program; if not, write to the
21 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 Boston, MA 02111-1307, USA.
23
24 Author: Alexander Larsson <alexl@redhat.com>
25 */
26
27 #include <config.h>
28 #include "nautilus-desktop-directory.h"
29
30 #include "nautilus-directory-private.h"
31 #include "nautilus-file.h"
32 #include "nautilus-file-private.h"
33 #include "nautilus-file-utilities.h"
34 #include "nautilus-global-preferences.h"
35 #include <gtk/gtk.h>
36
37 struct NautilusDesktopDirectoryDetails {
38 NautilusDirectory *real_directory;
39 GHashTable *callbacks;
40 GHashTable *monitors;
41 };
42
43 typedef struct {
44 NautilusDesktopDirectory *desktop_dir;
45 NautilusDirectoryCallback callback;
46 gpointer callback_data;
47
48 NautilusFileAttributes wait_for_attributes;
49 gboolean wait_for_file_list;
50
51 GList *non_ready_directories;
52 GList *merged_file_list;
53 } MergedCallback;
54
55
56 typedef struct {
57 NautilusDesktopDirectory *desktop_dir;
58
59 gboolean monitor_hidden_files;
60 NautilusFileAttributes monitor_attributes;
61 } MergedMonitor;
62
63 static void desktop_directory_changed_callback (gpointer data);
64
65 G_DEFINE_TYPE (NautilusDesktopDirectory, nautilus_desktop_directory,
66 NAUTILUS_TYPE_DIRECTORY);
67
68 static gboolean
69 desktop_contains_file (NautilusDirectory *directory,
70 NautilusFile *file)
71 {
72 NautilusDesktopDirectory *desktop;
73
74 desktop = NAUTILUS_DESKTOP_DIRECTORY (directory);
75
76 if (nautilus_directory_contains_file (desktop->details->real_directory, file)) {
77 return TRUE;
78 }
79
80 return file->details->directory == directory;
81 }
82
83 static guint
84 merged_callback_hash (gconstpointer merged_callback_as_pointer)
85 {
86 const MergedCallback *merged_callback;
87
88 merged_callback = merged_callback_as_pointer;
89 return GPOINTER_TO_UINT (merged_callback->callback)
90 ^ GPOINTER_TO_UINT (merged_callback->callback_data);
91 }
92
93 static gboolean
94 merged_callback_equal (gconstpointer merged_callback_as_pointer,
95 gconstpointer merged_callback_as_pointer_2)
96 {
97 const MergedCallback *merged_callback, *merged_callback_2;
98
99 merged_callback = merged_callback_as_pointer;
100 merged_callback_2 = merged_callback_as_pointer_2;
101
102 return merged_callback->callback == merged_callback_2->callback
103 && merged_callback->callback_data == merged_callback_2->callback_data;
104 }
105
106 static void
107 merged_callback_destroy (MergedCallback *merged_callback)
108 {
109 g_assert (merged_callback != NULL);
110 g_assert (NAUTILUS_IS_DESKTOP_DIRECTORY (merged_callback->desktop_dir));
111
112 g_list_free (merged_callback->non_ready_directories);
113 nautilus_file_list_free (merged_callback->merged_file_list);
114 g_free (merged_callback);
115 }
116
117 static void
118 merged_callback_check_done (MergedCallback *merged_callback)
119 {
120 /* Check if we are ready. */
121 if (merged_callback->non_ready_directories != NULL) {
122 return;
123 }
124
125 /* Remove from the hash table before sending it. */
126 g_hash_table_steal (merged_callback->desktop_dir->details->callbacks, merged_callback);
127
128 /* We are ready, so do the real callback. */
129 (* merged_callback->callback) (NAUTILUS_DIRECTORY (merged_callback->desktop_dir),
130 merged_callback->merged_file_list,
131 merged_callback->callback_data);
132
133 /* And we are done. */
134 merged_callback_destroy (merged_callback);
135 }
136
137 static void
138 merged_callback_remove_directory (MergedCallback *merged_callback,
139 NautilusDirectory *directory)
140 {
141 merged_callback->non_ready_directories = g_list_remove
142 (merged_callback->non_ready_directories, directory);
143 merged_callback_check_done (merged_callback);
144 }
145
146 static void
147 directory_ready_callback (NautilusDirectory *directory,
148 GList *files,
149 gpointer callback_data)
150 {
151 MergedCallback *merged_callback;
152
153 g_assert (NAUTILUS_IS_DIRECTORY (directory));
154 g_assert (callback_data != NULL);
155
156 merged_callback = callback_data;
157 g_assert (g_list_find (merged_callback->non_ready_directories, directory) != NULL);
158
159 /* Update based on this call. */
160 merged_callback->merged_file_list = g_list_concat
161 (merged_callback->merged_file_list,
162 nautilus_file_list_copy (files));
163
164 /* Check if we are ready. */
165 merged_callback_remove_directory (merged_callback, directory);
166 }
167
168 static void
169 desktop_call_when_ready (NautilusDirectory *directory,
170 NautilusFileAttributes file_attributes,
171 gboolean wait_for_file_list,
172 NautilusDirectoryCallback callback,
173 gpointer callback_data)
174 {
175 NautilusDesktopDirectory *desktop;
176 MergedCallback search_key, *merged_callback;
177
178 desktop = NAUTILUS_DESKTOP_DIRECTORY (directory);
179
180 /* Check to be sure we aren't overwriting. */
181 search_key.callback = callback;
182 search_key.callback_data = callback_data;
183 if (g_hash_table_lookup (desktop->details->callbacks, &search_key) != NULL) {
184 g_warning ("tried to add a new callback while an old one was pending");
185 return;
186 }
187
188 /* Create a merged_callback record. */
189 merged_callback = g_new0 (MergedCallback, 1);
190 merged_callback->desktop_dir = desktop;
191 merged_callback->callback = callback;
192 merged_callback->callback_data = callback_data;
193 merged_callback->wait_for_attributes = file_attributes;
194 merged_callback->wait_for_file_list = wait_for_file_list;
195 merged_callback->non_ready_directories = g_list_prepend
196 (merged_callback->non_ready_directories, directory);
197 merged_callback->non_ready_directories = g_list_prepend
198 (merged_callback->non_ready_directories, desktop->details->real_directory);
199
200
201 merged_callback->merged_file_list = g_list_concat (NULL,
202 nautilus_file_list_copy (directory->details->file_list));
203
204 /* Put it in the hash table. */
205 g_hash_table_insert (desktop->details->callbacks,
206 merged_callback, merged_callback);
207
208 /* Now tell all the directories about it. */
209 nautilus_directory_call_when_ready
210 (desktop->details->real_directory,
211 merged_callback->wait_for_attributes,
212 merged_callback->wait_for_file_list,
213 directory_ready_callback, merged_callback);
214 nautilus_directory_call_when_ready_internal
215 (directory,
216 NULL,
217 merged_callback->wait_for_attributes,
218 merged_callback->wait_for_file_list,
219 directory_ready_callback,
220 NULL,
221 merged_callback);
222
223 }
224
225 static void
226 desktop_cancel_callback (NautilusDirectory *directory,
227 NautilusDirectoryCallback callback,
228 gpointer callback_data)
229 {
230 NautilusDesktopDirectory *desktop;
231 MergedCallback search_key, *merged_callback;
232 GList *node;
233
234 desktop = NAUTILUS_DESKTOP_DIRECTORY (directory);
235
236 /* Find the entry in the table. */
237 search_key.callback = callback;
238 search_key.callback_data = callback_data;
239 merged_callback = g_hash_table_lookup (desktop->details->callbacks, &search_key);
240 if (merged_callback == NULL) {
241 return;
242 }
243
244 /* Remove from the hash table before working with it. */
245 g_hash_table_steal (merged_callback->desktop_dir->details->callbacks, merged_callback);
246
247 /* Tell all the directories to cancel the call. */
248 for (node = merged_callback->non_ready_directories; node != NULL; node = node->next) {
249 nautilus_directory_cancel_callback
250 (node->data,
251 directory_ready_callback, merged_callback);
252 }
253 merged_callback_destroy (merged_callback);
254 }
255
256 static void
257 merged_monitor_destroy (MergedMonitor *monitor)
258 {
259 NautilusDesktopDirectory *desktop;
260
261 desktop = monitor->desktop_dir;
262
263 /* Call through to the real directory remove calls. */
264 nautilus_directory_file_monitor_remove (desktop->details->real_directory, monitor);
265
266 nautilus_directory_monitor_remove_internal (NAUTILUS_DIRECTORY (desktop), NULL, monitor);
267
268 g_free (monitor);
269 }
270
271 static void
272 build_merged_callback_list (NautilusDirectory *directory,
273 GList *file_list,
274 gpointer callback_data)
275 {
276 GList **merged_list;
277
278 merged_list = callback_data;
279 *merged_list = g_list_concat (*merged_list,
280 nautilus_file_list_copy (file_list));
281 }
282
283 static void
284 desktop_monitor_add (NautilusDirectory *directory,
285 gconstpointer client,
286 gboolean monitor_hidden_files,
287 NautilusFileAttributes file_attributes,
288 NautilusDirectoryCallback callback,
289 gpointer callback_data)
290 {
291 NautilusDesktopDirectory *desktop;
292 MergedMonitor *monitor;
293 GList *merged_callback_list;
294
295 desktop = NAUTILUS_DESKTOP_DIRECTORY (directory);
296
297 /* Map the client to a unique value so this doesn't interfere
298 * with direct monitoring of the directory by the same client.
299 */
300 monitor = g_hash_table_lookup (desktop->details->monitors, client);
301 if (monitor != NULL) {
302 g_assert (monitor->desktop_dir == desktop);
303 } else {
304 monitor = g_new0 (MergedMonitor, 1);
305 monitor->desktop_dir = desktop;
306 g_hash_table_insert (desktop->details->monitors,
307 (gpointer) client, monitor);
308 }
309 monitor->monitor_hidden_files = monitor_hidden_files;
310 monitor->monitor_attributes = file_attributes;
311
312 /* Call through to the real directory add calls. */
313 merged_callback_list = NULL;
314
315 /* Call up to real dir */
316 nautilus_directory_file_monitor_add
317 (desktop->details->real_directory, monitor,
318 monitor_hidden_files,
319 file_attributes,
320 build_merged_callback_list, &merged_callback_list);
321
322 /* Handle the desktop part */
323 merged_callback_list = g_list_concat (merged_callback_list,
324 nautilus_file_list_copy (directory->details->file_list));
325
326
327 if (callback != NULL) {
328 (* callback) (directory, merged_callback_list, callback_data);
329 }
330 nautilus_file_list_free (merged_callback_list);
331 }
332
333 static void
334 desktop_monitor_remove (NautilusDirectory *directory,
335 gconstpointer client)
336 {
337 NautilusDesktopDirectory *desktop;
338 MergedMonitor *monitor;
339
340 desktop = NAUTILUS_DESKTOP_DIRECTORY (directory);
341
342 monitor = g_hash_table_lookup (desktop->details->monitors, client);
343 if (monitor == NULL) {
344 return;
345 }
346
347 g_hash_table_remove (desktop->details->monitors, client);
348 }
349
350 static void
351 desktop_force_reload (NautilusDirectory *directory)
352 {
353 NautilusDesktopDirectory *desktop;
354
355 desktop = NAUTILUS_DESKTOP_DIRECTORY (directory);
356
357 nautilus_directory_force_reload (desktop->details->real_directory);
358
359 /* We don't invalidate the files in desktop, since they are always
360 up to date. (And we don't ever want to mark them invalid.) */
361 }
362
363 static gboolean
364 desktop_are_all_files_seen (NautilusDirectory *directory)
365 {
366 NautilusDesktopDirectory *desktop;
367
368 desktop = NAUTILUS_DESKTOP_DIRECTORY (directory);
369
370 if (!nautilus_directory_are_all_files_seen (desktop->details->real_directory)) {
371 return FALSE;
372 }
373
374 return TRUE;
375 }
376
377 static gboolean
378 desktop_is_not_empty (NautilusDirectory *directory)
379 {
380 NautilusDesktopDirectory *desktop;
381
382 desktop = NAUTILUS_DESKTOP_DIRECTORY (directory);
383
384 if (nautilus_directory_is_not_empty (desktop->details->real_directory)) {
385 return TRUE;
386 }
387
388 return directory->details->file_list != NULL;
389 }
390
391 static GList *
392 desktop_get_file_list (NautilusDirectory *directory)
393 {
394 GList *real_dir_file_list, *desktop_dir_file_list = NULL;
395
396 real_dir_file_list = nautilus_directory_get_file_list
397 (NAUTILUS_DESKTOP_DIRECTORY (directory)->details->real_directory);
398 desktop_dir_file_list = NAUTILUS_DIRECTORY_CLASS (nautilus_desktop_directory_parent_class)->get_file_list (directory);
399
400 return g_list_concat (real_dir_file_list, desktop_dir_file_list);
401 }
402
403 NautilusDirectory *
404 nautilus_desktop_directory_get_real_directory (NautilusDesktopDirectory *desktop)
405 {
406 nautilus_directory_ref (desktop->details->real_directory);
407 return desktop->details->real_directory;
408 }
409
410
411 static void
412 desktop_finalize (GObject *object)
413 {
414 NautilusDesktopDirectory *desktop;
415
416 desktop = NAUTILUS_DESKTOP_DIRECTORY (object);
417
418 nautilus_directory_unref (desktop->details->real_directory);
419
420 g_hash_table_destroy (desktop->details->callbacks);
421 g_hash_table_destroy (desktop->details->monitors);
422 g_free (desktop->details);
423
424 g_signal_handlers_disconnect_by_func (nautilus_preferences,
425 desktop_directory_changed_callback,
426 desktop);
427
428 G_OBJECT_CLASS (nautilus_desktop_directory_parent_class)->finalize (object);
429 }
430
431 static void
432 done_loading_callback (NautilusDirectory *real_directory,
433 NautilusDesktopDirectory *desktop)
434 {
435 nautilus_directory_emit_done_loading (NAUTILUS_DIRECTORY (desktop));
436 }
437
438
439 static void
440 forward_files_added_cover (NautilusDirectory *real_directory,
441 GList *files,
442 gpointer callback_data)
443 {
444 nautilus_directory_emit_files_added (NAUTILUS_DIRECTORY (callback_data), files);
445 }
446
447 static void
448 forward_files_changed_cover (NautilusDirectory *real_directory,
449 GList *files,
450 gpointer callback_data)
451 {
452 nautilus_directory_emit_files_changed (NAUTILUS_DIRECTORY (callback_data), files);
453 }
454
455 static void
456 update_desktop_directory (NautilusDesktopDirectory *desktop)
457 {
458 char *desktop_path;
459 char *desktop_uri;
460 NautilusDirectory *real_directory;
461
462 real_directory = desktop->details->real_directory;
463 if (real_directory != NULL) {
464 g_hash_table_foreach_remove (desktop->details->callbacks, (GHRFunc) gtk_true, NULL);
465 g_hash_table_foreach_remove (desktop->details->monitors, (GHRFunc) gtk_true, NULL);
466
467 g_signal_handlers_disconnect_by_func (real_directory, done_loading_callback, desktop);
468 g_signal_handlers_disconnect_by_func (real_directory, forward_files_added_cover, desktop);
469 g_signal_handlers_disconnect_by_func (real_directory, forward_files_changed_cover, desktop);
470
471 nautilus_directory_unref (real_directory);
472 }
473
474 desktop_path = nautilus_get_desktop_directory ();
475 desktop_uri = g_filename_to_uri (desktop_path, NULL, NULL);
476 real_directory = nautilus_directory_get_by_uri (desktop_uri);
477 g_free (desktop_uri);
478 g_free (desktop_path);
479
480 g_signal_connect_object (real_directory, "done_loading",
481 G_CALLBACK (done_loading_callback), desktop, 0);
482 g_signal_connect_object (real_directory, "files_added",
483 G_CALLBACK (forward_files_added_cover), desktop, 0);
484 g_signal_connect_object (real_directory, "files_changed",
485 G_CALLBACK (forward_files_changed_cover), desktop, 0);
486
487 desktop->details->real_directory = real_directory;
488 }
489
490 static void
491 desktop_directory_changed_callback (gpointer data)
492 {
493 update_desktop_directory (NAUTILUS_DESKTOP_DIRECTORY (data));
494 nautilus_directory_force_reload (NAUTILUS_DIRECTORY (data));
495 }
496
497 static void
498 nautilus_desktop_directory_init (NautilusDesktopDirectory *desktop)
499 {
500 desktop->details = g_new0 (NautilusDesktopDirectoryDetails, 1);
501
502 desktop->details->callbacks = g_hash_table_new_full
503 (merged_callback_hash, merged_callback_equal,
504 NULL, (GDestroyNotify)merged_callback_destroy);
505 desktop->details->monitors = g_hash_table_new_full (NULL, NULL,
506 NULL, (GDestroyNotify)merged_monitor_destroy);
507
508 update_desktop_directory (NAUTILUS_DESKTOP_DIRECTORY (desktop));
509 }
510
511 static void
512 nautilus_desktop_directory_class_init (NautilusDesktopDirectoryClass *class)
513 {
514 NautilusDirectoryClass *directory_class;
515
516 directory_class = NAUTILUS_DIRECTORY_CLASS (class);
517
518 G_OBJECT_CLASS (class)->finalize = desktop_finalize;
519
520 directory_class->contains_file = desktop_contains_file;
521 directory_class->call_when_ready = desktop_call_when_ready;
522 directory_class->cancel_callback = desktop_cancel_callback;
523 directory_class->file_monitor_add = desktop_monitor_add;
524 directory_class->file_monitor_remove = desktop_monitor_remove;
525 directory_class->force_reload = desktop_force_reload;
526 directory_class->are_all_files_seen = desktop_are_all_files_seen;
527 directory_class->is_not_empty = desktop_is_not_empty;
528 /* Override get_file_list so that we can return the list of files
529 * in NautilusDesktopDirectory->details->real_directory,
530 * in addition to the list of standard desktop icons on the desktop.
531 */
532 directory_class->get_file_list = desktop_get_file_list;
533 }
534