2007-12-11 A. Walton <awalton@svn.gnome.org>
[nautilus.git] / libnautilus-private / nautilus-metafile.c
blobc1fe9edbde248ff78a940f939868c4782acd1220
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-*/
3 /* nautilus-metafile.c - server side of Nautilus::Metafile
5 * Copyright (C) 2001 Eazel, Inc.
6 * Copyright (C) 2001-2005 Free Software Foundation, Inc.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library 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 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
24 #include <config.h>
25 #include "nautilus-metafile.h"
27 #include "nautilus-directory.h"
28 #include "nautilus-directory.h"
29 #include "nautilus-file-private.h"
30 #include "nautilus-file-utilities.h"
31 #include "nautilus-global-preferences.h"
32 #include "nautilus-metadata.h"
33 #include "nautilus-thumbnails.h"
34 #include <eel/eel-glib-extensions.h>
35 #include <eel/eel-gtk-macros.h>
36 #include <eel/eel-string.h>
37 #include <eel/eel-vfs-extensions.h>
38 #include <eel/eel-xml-extensions.h>
39 #include <glib/gurifuncs.h>
40 #include <libxml/parser.h>
41 #include <gtk/gtkmain.h>
42 #include <stdlib.h>
43 #include <time.h>
44 #include <unistd.h>
46 #define METAFILE_XML_VERSION "1.0"
47 #define METAFILE_PERMISSIONS 0600
48 #define METAFILES_DIRECTORY_NAME "metafiles"
50 enum {
51 CHANGED,
52 READY,
53 LAST_SIGNAL
56 static guint signals[LAST_SIGNAL] = { 0 };
58 /* TODO asynchronous copying/moving of metadata
60 * - potential metadata loss when a deletion is scheduled, and new metadata is copied to
61 * this location before the old deletion is consumed
63 * - if metafile read fails, and a file from that metafile is scheduled for a copy/move operation,
64 * its associated operations are not removed from pending_copies
66 * */
68 static char *get_file_metadata (NautilusMetafile *metafile,
69 const char *file_name,
70 const char *key,
71 const char *default_metadata);
72 static GList *get_file_metadata_list (NautilusMetafile *metafile,
73 const char *file_name,
74 const char *list_key,
75 const char *list_subkey);
76 static gboolean set_file_metadata (NautilusMetafile *metafile,
77 const char *file_name,
78 const char *key,
79 const char *default_metadata,
80 const char *metadata);
81 static gboolean set_file_metadata_list (NautilusMetafile *metafile,
82 const char *file_name,
83 const char *list_key,
84 const char *list_subkey,
85 GList *list);
86 static void rename_file_metadata (NautilusMetafile *metafile,
87 const char *old_file_name,
88 const char *new_file_name);
89 static void copy_file_metadata (NautilusMetafile *source_metafile,
90 const char *source_file_name,
91 NautilusMetafile *destination_metafile,
92 const char *destination_file_name);
93 static void real_copy_file_metadata (NautilusMetafile *source_metafile,
94 const char *source_file_name,
95 NautilusMetafile *destination_metafile,
96 const char *destination_file_name);
97 static void remove_file_metadata (NautilusMetafile *metafile,
98 const char *file_name);
99 static void real_remove_file_metadata (NautilusMetafile *metafile,
100 const char *file_name);
101 static void call_metafile_changed_for_one_file (NautilusMetafile *metafile,
102 const char *file_name);
103 static void metafile_read_restart (NautilusMetafile *metafile);
104 static void metafile_read_start (NautilusMetafile *metafile);
105 static void metafile_write_start (NautilusMetafile *metafile);
106 static void directory_request_write_metafile (NautilusMetafile *metafile);
107 static void metafile_free_metadata (NautilusMetafile *metafile);
108 static void metafile_read_cancel (NautilusMetafile *metafile);
109 static void async_read_cancel (NautilusMetafile *metafile);
110 static void set_metafile_contents (NautilusMetafile *metafile,
111 xmlDocPtr metafile_contents);
113 typedef struct MetafileReadState {
114 NautilusMetafile *metafile;
115 GCancellable *cancellable;
116 } MetafileReadState;
118 typedef struct MetafileWriteState {
119 xmlChar *buffer;
120 goffset size;
121 gboolean write_again;
122 } MetafileWriteState;
124 struct _NautilusMetafile {
125 GObject parent_slot;
127 gboolean is_read;
129 xmlDoc *xml;
130 GHashTable *node_hash;
131 GHashTable *changes;
133 /* State for reading and writing metadata. */
134 MetafileReadState *read_state;
135 guint write_idle_id;
136 MetafileWriteState *write_state;
138 GList *monitors;
140 char *private_uri;
141 char *directory_uri;
144 static GHashTable *metafiles;
146 G_DEFINE_TYPE (NautilusMetafile, nautilus_metafile, G_TYPE_OBJECT);
148 static void
149 nautilus_metafile_init (NautilusMetafile *metafile)
151 metafile->node_hash = g_hash_table_new (g_str_hash, g_str_equal);
155 static void
156 finalize (GObject *object)
158 NautilusMetafile *metafile;
160 metafile = NAUTILUS_METAFILE (object);
162 g_assert (metafile->write_state == NULL);
163 async_read_cancel (metafile);
164 g_assert (metafile->read_state == NULL);
166 g_hash_table_remove (metafiles, metafile->directory_uri);
168 metafile_free_metadata (metafile);
169 g_hash_table_destroy (metafile->node_hash);
171 g_assert (metafile->write_idle_id == 0);
173 g_free (metafile->private_uri);
174 g_free (metafile->directory_uri);
176 G_OBJECT_CLASS (nautilus_metafile_parent_class)->finalize (object);
179 static char *
180 escape_slashes (const char *str)
182 int n_reserved;
183 const char *p;
184 char *escaped, *e;
186 n_reserved = 0;
187 for (p = str; *p != 0; p++) {
188 if (*p == '%' || *p == '/') {
189 n_reserved++;
193 escaped = g_malloc (strlen (str) + 2*n_reserved + 1);
195 e = escaped;
197 for (p = str; *p != 0; p++) {
198 if (*p == '%') {
199 *e++ = '%';
200 *e++ = '2';
201 *e++ = '5';
202 } else if (*p == '/') {
203 *e++ = '%';
204 *e++ = '2';
205 *e++ = 'f';
206 } else {
207 *e++ = *p;
210 *e = 0;
212 return escaped;
216 static char *
217 construct_private_metafile_uri (const char *uri)
219 char *user_directory, *metafiles_directory;
220 char *escaped_uri, *file_name;
221 char *alternate_path, *alternate_uri;
223 /* Ensure that the metafiles directory exists. */
224 user_directory = nautilus_get_user_directory ();
225 metafiles_directory = g_build_filename (user_directory, METAFILES_DIRECTORY_NAME, NULL);
226 g_free (user_directory);
227 mkdir (metafiles_directory, 0700);
229 /* Construct a file name from the URI. */
230 escaped_uri = escape_slashes (uri);
231 file_name = g_strconcat (escaped_uri, ".xml", NULL);
232 g_free (escaped_uri);
234 /* Construct a URI for something in the "metafiles" directory. */
235 alternate_path = g_build_filename (metafiles_directory, file_name, NULL);
236 g_free (metafiles_directory);
237 g_free (file_name);
238 alternate_uri = g_filename_to_uri (alternate_path, NULL, NULL);
239 g_free (alternate_path);
241 return alternate_uri;
244 static void
245 nautilus_metafile_set_directory_uri (NautilusMetafile *metafile,
246 const char *directory_uri)
248 if (eel_strcmp (metafile->directory_uri, directory_uri) == 0) {
249 return;
252 g_free (metafile->directory_uri);
253 metafile->directory_uri = g_strdup (directory_uri);
255 g_free (metafile->private_uri);
256 metafile->private_uri
257 = construct_private_metafile_uri (directory_uri);
260 static NautilusMetafile *
261 nautilus_metafile_new (const char *directory_uri)
263 NautilusMetafile *metafile;
265 metafile = NAUTILUS_METAFILE (g_object_new (NAUTILUS_TYPE_METAFILE, NULL));
267 nautilus_metafile_set_directory_uri (metafile, directory_uri);
269 return metafile;
272 NautilusMetafile *
273 nautilus_metafile_get_for_uri (const char *directory_uri)
275 NautilusMetafile *metafile;
276 char *canonical_uri;
277 GFile *file;
279 g_return_val_if_fail (directory_uri != NULL, NULL);
281 if (metafiles == NULL) {
282 metafiles = eel_g_hash_table_new_free_at_exit
283 (g_str_hash, g_str_equal, __FILE__ ": metafiles");
287 file = g_file_new_for_uri (directory_uri);
288 canonical_uri = g_file_get_uri (file);
289 g_object_unref (file);
291 metafile = g_hash_table_lookup (metafiles, canonical_uri);
293 if (metafile != NULL) {
294 g_object_ref (metafile);
295 } else {
296 metafile = nautilus_metafile_new (canonical_uri);
298 g_assert (strcmp (metafile->directory_uri, canonical_uri) == 0);
300 g_hash_table_insert (metafiles,
301 metafile->directory_uri,
302 metafile);
305 g_free (canonical_uri);
307 return metafile;
310 static GList *pending_copies;
312 typedef struct {
313 NautilusMetafile *source_metafile;
314 char *source_file_name;
315 NautilusMetafile *destination_metafile;
316 char *destination_file_name;
317 } NautilusMetadataCopy;
319 static gboolean
320 nautilus_metadata_copy_equal (const NautilusMetadataCopy *a,
321 const NautilusMetadataCopy *b)
323 return (b->source_metafile == a->source_metafile)
324 && (b->destination_metafile == a->destination_metafile)
325 && (strcmp (a->source_file_name, b->source_file_name) == 0)
326 && (strcmp (a->destination_file_name, b->destination_file_name) == 0);
329 static NautilusMetadataCopy *
330 nautilus_metadata_get_scheduled_copy (NautilusMetafile *source_metafile,
331 const char *source_file_name,
332 NautilusMetafile *destination_metafile,
333 const char *destination_file_name)
335 NautilusMetadataCopy key, *copy;
336 GList *l;
338 key.source_metafile = source_metafile;
339 key.source_file_name = (char *) source_file_name;
340 key.destination_metafile = destination_metafile;
341 key.destination_file_name = (char *) destination_file_name;
343 for (l = pending_copies; l != NULL; l = l->next) {
344 copy = l->data;
346 if (nautilus_metadata_copy_equal (l->data, &key)) {
347 return copy;
351 return NULL;
354 static gboolean
355 nautilus_metadata_has_scheduled_copy (NautilusMetafile *source_metafile,
356 const char *source_file_name)
358 NautilusMetadataCopy *copy;
359 GList *l;
361 for (l = pending_copies; l != NULL; l = l->next) {
362 copy = l->data;
364 if ((copy->source_metafile == source_metafile) &&
365 (strcmp (copy->source_file_name, source_file_name) == 0)) {
366 return TRUE;
370 return FALSE;
373 static void
374 nautilus_metadata_schedule_copy (NautilusMetafile *source_metafile,
375 const char *source_file_name,
376 NautilusMetafile *destination_metafile,
377 const char *destination_file_name)
379 NautilusMetadataCopy *copy;
381 g_assert (!source_metafile->is_read || !destination_metafile->is_read);
383 copy = nautilus_metadata_get_scheduled_copy (source_metafile,
384 source_file_name,
385 destination_metafile,
386 destination_file_name);
387 if (copy == NULL) {
388 copy = g_malloc (sizeof (NautilusMetadataCopy));
389 copy->source_metafile = g_object_ref (source_metafile);
390 copy->source_file_name = g_strdup (source_file_name);
391 copy->destination_metafile = g_object_ref (destination_metafile);
392 copy->destination_file_name = g_strdup (destination_file_name);
394 pending_copies = g_list_prepend (pending_copies, copy);
396 metafile_read_start (source_metafile);
397 metafile_read_start (destination_metafile);
401 static void
402 nautilus_metadata_process_ready_copies (void)
404 NautilusMetadataCopy *copy;
405 GList *l, *next;
407 l = pending_copies;
408 while (l != NULL) {
409 copy = l->data;
411 next = l->next;
413 if (copy->source_metafile->is_read &&
414 copy->destination_metafile->is_read) {
415 real_copy_file_metadata (copy->source_metafile, copy->source_file_name,
416 copy->destination_metafile, copy->destination_file_name);
418 g_object_unref (copy->source_metafile);
419 g_free (copy->source_file_name);
420 g_object_unref (copy->destination_metafile);
421 g_free (copy->destination_file_name);
422 g_free (copy);
424 pending_copies = g_list_delete_link (pending_copies, l);
427 l = next;
431 static GList *pending_removals;
433 typedef struct {
434 NautilusMetafile *metafile;
435 char *file_name;
436 } NautilusMetadataRemoval;
438 static gboolean
439 nautilus_metadata_removal_equal (const NautilusMetadataRemoval *a,
440 const NautilusMetadataRemoval *b)
442 return ((b->metafile == a->metafile) &&
443 (strcmp (a->file_name, b->file_name) == 0));
446 static NautilusMetadataRemoval *
447 nautilus_metadata_get_scheduled_removal (NautilusMetafile *metafile,
448 const char *file_name)
450 NautilusMetadataRemoval key, *removal;
451 GList *l;
453 key.metafile = metafile;
454 key.file_name = (char *) file_name;
456 for (l = pending_removals; l != NULL; l = l->next) {
457 removal = l->data;
459 if (nautilus_metadata_removal_equal (l->data, &key)) {
460 return removal;
464 return NULL;
467 static void
468 nautilus_metadata_schedule_removal (NautilusMetafile *metafile,
469 const char *file_name)
471 NautilusMetadataRemoval *removal;
473 g_assert (nautilus_metadata_has_scheduled_copy (metafile, file_name));
475 removal = nautilus_metadata_get_scheduled_removal (metafile, file_name);
476 if (removal == NULL) {
477 removal = g_malloc (sizeof (NautilusMetadataRemoval));
478 removal->metafile = g_object_ref (metafile);
479 removal->file_name = g_strdup (file_name);
481 pending_removals = g_list_prepend (pending_removals, removal);
485 static void
486 nautilus_metadata_process_ready_removals (void)
488 NautilusMetadataRemoval *removal;
489 GList *l, *next;
491 l = pending_removals;
492 while (l != NULL) {
493 removal = l->data;
495 next = l->next;
497 if (!nautilus_metadata_has_scheduled_copy (removal->metafile, removal->file_name)) {
498 real_remove_file_metadata (removal->metafile, removal->file_name);
500 pending_removals = g_list_delete_link (pending_removals, l);
502 g_object_unref (removal->metafile);
503 g_free (removal->file_name);
506 l = next;
510 /* FIXME
511 * Right now we only limit the number of conccurrent reads.
512 * We may want to consider limiting writes as well.
515 static int num_reads_in_progress;
516 static GList *pending_reads;
518 #if 0
519 #define DEBUG_METADATA_IO
520 #endif
522 static void
523 schedule_next_read (void)
525 const int kMaxAsyncReads = 10;
527 GList* node;
529 #ifdef DEBUG_METADATA_IO
530 g_message ("schedule_next_read: %d pending reads, %d reads in progress",
531 g_list_length (pending_reads), num_reads_in_progress);
532 #endif
534 if (pending_reads != NULL && num_reads_in_progress <= kMaxAsyncReads) {
535 node = pending_reads;
536 pending_reads = g_list_remove_link (pending_reads, node);
537 #ifdef DEBUG_METADATA_IO
538 g_message ("schedule_next_read: %s", NAUTILUS_METAFILE (node->data)->details->directory_uri);
539 #endif
540 metafile_read_start (node->data);
541 g_list_free_1 (node);
542 ++num_reads_in_progress;
546 static void
547 async_read_start (NautilusMetafile *metafile)
549 if (metafile->is_read ||
550 metafile->read_state != NULL) {
551 return;
553 #ifdef DEBUG_METADATA_IO
554 g_message ("async_read_start: %s", metafile->directory_uri);
555 #endif
556 pending_reads = g_list_prepend (pending_reads, metafile);
557 schedule_next_read ();
560 static void
561 async_read_done (NautilusMetafile *metafile)
563 #ifdef DEBUG_METADATA_IO
564 g_message ("async_read_done: %s", metafile->directory_uri);
565 #endif
566 --num_reads_in_progress;
567 schedule_next_read ();
570 static void
571 async_read_cancel (NautilusMetafile *metafile)
573 GList* node;
575 #ifdef DEBUG_METADATA_IO
576 g_message ("async_read_cancel: %s", metafile->directory_uri);
577 #endif
578 node = g_list_find (pending_reads, metafile);
580 if (node != NULL) {
581 pending_reads = g_list_remove_link (pending_reads, node);
582 g_list_free_1 (node);
585 if (metafile->read_state != NULL) {
586 metafile_read_cancel (metafile);
587 async_read_done (metafile);
592 gboolean
593 nautilus_metafile_is_read (NautilusMetafile *metafile)
595 return metafile->is_read;
598 char *
599 nautilus_metafile_get (NautilusMetafile *metafile,
600 const char *file_name,
601 const char *key,
602 const char *default_value)
604 return get_file_metadata (metafile, file_name, key, default_value);
607 GList *
608 nautilus_metafile_get_list (NautilusMetafile *metafile,
609 const char *file_name,
610 const char *list_key,
611 const char *list_subkey)
613 return get_file_metadata_list (metafile, file_name, list_key, list_subkey);
617 void
618 nautilus_metafile_set (NautilusMetafile *metafile,
619 const char *file_name,
620 const char *key,
621 const char *default_value,
622 const char *metadata)
624 if (set_file_metadata (metafile, file_name, key, default_value, metadata)) {
625 call_metafile_changed_for_one_file (metafile, file_name);
629 void
630 nautilus_metafile_set_list (NautilusMetafile *metafile,
631 const char *file_name,
632 const char *list_key,
633 const char *list_subkey,
634 GList *list)
636 if (set_file_metadata_list (metafile, file_name, list_key, list_subkey, list)) {
637 call_metafile_changed_for_one_file (metafile, file_name);
641 void
642 nautilus_metafile_copy (NautilusMetafile *source_metafile,
643 const char *source_file_name,
644 const char *destination_directory_uri,
645 const char *destination_file_name)
647 NautilusMetafile *destination_metafile;
649 destination_metafile = nautilus_metafile_get_for_uri (destination_directory_uri);
651 copy_file_metadata (source_metafile, source_file_name,
652 destination_metafile, destination_file_name);
654 g_object_unref (destination_metafile);
658 void
659 nautilus_metafile_remove (NautilusMetafile *metafile,
660 const char *file_name)
662 remove_file_metadata (metafile, file_name);
665 void
666 nautilus_metafile_rename (NautilusMetafile *metafile,
667 const char *old_file_name,
668 const char *new_file_name)
670 rename_file_metadata (metafile, old_file_name, new_file_name);
673 void
674 nautilus_metafile_rename_directory (NautilusMetafile *metafile,
675 const char *new_directory_uri)
677 nautilus_metafile_set_directory_uri (metafile, new_directory_uri);
680 void
681 nautilus_metafile_load (NautilusMetafile *metafile)
683 async_read_start (metafile);
686 static gboolean
687 notify_metafile_ready_idle (gpointer user_data)
689 NautilusMetafile *metafile;
690 metafile = user_data;
692 g_signal_emit (metafile, signals[READY], 0);
693 g_object_unref (metafile);
694 return FALSE;
697 static void
698 nautilus_metafile_notify_metafile_ready (NautilusMetafile *metafile, gboolean in_idle)
700 if (in_idle) {
701 g_idle_add (notify_metafile_ready_idle, g_object_ref (metafile));
702 } else {
703 g_signal_emit (metafile, signals[READY], 0);
707 typedef struct {
708 NautilusMetafile *metafile;
709 GList *file_names;
710 } ChangedData;
712 static gboolean
713 metafile_changed_idle (gpointer user_data)
715 ChangedData *data;
717 data = user_data;
719 g_signal_emit (data->metafile, signals[CHANGED], 0, data->file_names);
721 g_object_unref (data->metafile);
722 eel_g_list_free_deep (data->file_names);
723 g_free (data);
725 return FALSE;
728 static void
729 call_metafile_changed (NautilusMetafile *metafile,
730 GList *file_names)
732 ChangedData *data;
734 data = g_new (ChangedData, 1);
735 data->metafile = g_object_ref (metafile);
736 data->file_names = eel_g_str_list_copy (file_names);
738 g_idle_add (metafile_changed_idle, data);
741 #if 0
742 static void
743 file_list_filler_ghfunc (gpointer key,
744 gpointer value,
745 gpointer user_data)
747 Nautilus_FileNameList *file_names;
749 file_names = user_data;
751 file_names->_buffer [file_names->_length] = key;
753 ++file_names->_length;
756 void
757 call_metafile_changed_for_all_files_mentioned_in_metafile (NautilusMetafile *metafile)
759 CORBA_unsigned_long len;
760 Nautilus_FileNameList file_names;
762 len = g_hash_table_size (metafile->node_hash);
764 if (len > 0) {
765 file_names._maximum = len;
766 file_names._length = 0;
767 file_names._buffer = g_new (CORBA_char *, len);
769 g_hash_table_foreach (metafile->node_hash,
770 file_list_filler_ghfunc,
771 &file_names);
773 call_metafile_changed (metafile, &file_names);
775 g_free (file_names._buffer);
778 #endif
780 static void
781 call_metafile_changed_for_one_file (NautilusMetafile *metafile,
782 const char *file_name)
784 GList l;
786 l.next = NULL;
787 l.prev = NULL;
788 l.data = (void *)file_name;
790 call_metafile_changed (metafile, &l);
793 typedef struct {
794 gboolean is_list;
795 union {
796 char *string;
797 GList *string_list;
798 } value;
799 char *default_value;
800 } MetadataValue;
802 static char *
803 get_metadata_from_node (xmlNode *node,
804 const char *key,
805 const char *default_metadata)
807 xmlChar *property;
808 char *result;
810 g_return_val_if_fail (key != NULL, NULL);
811 g_return_val_if_fail (key[0] != '\0', NULL);
813 property = xmlGetProp (node, key);
814 if (property == NULL) {
815 result = g_strdup (default_metadata);
816 } else {
817 result = g_strdup (property);
819 xmlFree (property);
821 return result;
824 static GList *
825 get_metadata_list_from_node (xmlNode *node,
826 const char *list_key,
827 const char *list_subkey)
829 return eel_xml_get_property_for_children
830 (node, list_key, list_subkey);
833 static xmlNode *
834 create_metafile_root (NautilusMetafile *metafile)
836 xmlNode *root;
838 if (metafile->xml == NULL) {
839 set_metafile_contents (metafile, xmlNewDoc (METAFILE_XML_VERSION));
841 root = xmlDocGetRootElement (metafile->xml);
842 if (root == NULL) {
843 root = xmlNewDocNode (metafile->xml, NULL, "directory", NULL);
844 xmlDocSetRootElement (metafile->xml, root);
847 return root;
850 static xmlNode *
851 get_file_node (NautilusMetafile *metafile,
852 const char *file_name,
853 gboolean create)
855 GHashTable *hash;
856 xmlNode *root, *node;
857 char *escaped_file_name;
859 g_assert (NAUTILUS_IS_METAFILE (metafile));
861 hash = metafile->node_hash;
862 node = g_hash_table_lookup (hash, file_name);
863 if (node != NULL) {
864 return node;
867 if (create) {
868 root = create_metafile_root (metafile);
869 node = xmlNewChild (root, NULL, "file", NULL);
870 escaped_file_name = g_uri_escape_string (file_name, NULL, 0);
871 xmlSetProp (node, "name", escaped_file_name);
872 g_free (escaped_file_name);
873 g_hash_table_insert (hash, xmlMemStrdup (file_name), node);
874 return node;
877 return NULL;
880 static void
881 set_file_node_timestamp (xmlNode *node)
883 char time_str[21];
885 /* 2^64 turns out to be 20 characters */
886 snprintf (time_str, 20, "%ld", time (NULL));
887 time_str [20] = '\0';
888 xmlSetProp (node, "timestamp", time_str);
891 static char *
892 get_metadata_string_from_metafile (NautilusMetafile *metafile,
893 const char *file_name,
894 const char *key,
895 const char *default_metadata)
897 xmlNode *node;
899 node = get_file_node (metafile, file_name, FALSE);
900 return get_metadata_from_node (node, key, default_metadata);
903 static GList *
904 get_metadata_list_from_metafile (NautilusMetafile *metafile,
905 const char *file_name,
906 const char *list_key,
907 const char *list_subkey)
909 xmlNode *node;
911 node = get_file_node (metafile, file_name, FALSE);
912 return get_metadata_list_from_node (node, list_key, list_subkey);
915 static gboolean
916 set_metadata_string_in_metafile (NautilusMetafile *metafile,
917 const char *file_name,
918 const char *key,
919 const char *default_metadata,
920 const char *metadata)
922 char *old_metadata;
923 gboolean old_metadata_matches;
924 const char *value;
925 xmlNode *node;
926 xmlAttr *property_node;
928 /* If the data in the metafile is already correct, do nothing. */
929 old_metadata = get_file_metadata
930 (metafile, file_name, key, default_metadata);
932 old_metadata_matches = eel_strcmp (old_metadata, metadata) == 0;
933 g_free (old_metadata);
934 if (old_metadata_matches) {
935 return FALSE;
938 /* Data that matches the default is represented in the tree by
939 * the lack of an attribute.
941 if (eel_strcmp (default_metadata, metadata) == 0) {
942 value = NULL;
943 } else {
944 value = metadata;
947 /* Get or create the node. */
948 node = get_file_node (metafile, file_name, value != NULL);
950 if (node != NULL) {
951 /* Set the timestamp */
952 set_file_node_timestamp (node);
954 /* Add or remove a property node. */
955 property_node = xmlSetProp (node, key, value);
956 if (value == NULL) {
957 xmlRemoveProp (property_node);
961 /* Since we changed the tree, arrange for it to be written. */
962 directory_request_write_metafile (metafile);
963 return TRUE;
966 static gboolean
967 set_metadata_list_in_metafile (NautilusMetafile *metafile,
968 const char *file_name,
969 const char *list_key,
970 const char *list_subkey,
971 GList *list)
973 xmlNode *node, *child, *next;
974 gboolean changed;
975 GList *p;
976 xmlChar *property;
978 /* Get or create the node. */
979 node = get_file_node (metafile, file_name, list != NULL);
981 /* Work with the list. */
982 changed = FALSE;
983 if (node == NULL) {
984 g_assert (list == NULL);
985 } else {
986 p = list;
988 /* Remove any nodes except the ones we expect. */
989 for (child = eel_xml_get_children (node);
990 child != NULL;
991 child = next) {
993 next = child->next;
994 if (strcmp (child->name, list_key) == 0) {
995 property = xmlGetProp (child, list_subkey);
996 if (property != NULL && p != NULL
997 && strcmp (property, (char *) p->data) == 0) {
998 p = p->next;
999 } else {
1000 xmlUnlinkNode (child);
1001 xmlFreeNode (child);
1002 changed = TRUE;
1004 xmlFree (property);
1008 /* Add any additional nodes needed. */
1009 for (; p != NULL; p = p->next) {
1010 child = xmlNewChild (node, NULL, list_key, NULL);
1011 xmlSetProp (child, list_subkey, p->data);
1012 changed = TRUE;
1015 /* Set the timestamp */
1016 set_file_node_timestamp (node);
1019 if (!changed) {
1020 return FALSE;
1023 directory_request_write_metafile (metafile);
1024 return TRUE;
1027 static MetadataValue *
1028 metadata_value_new (const char *default_metadata, const char *metadata)
1030 MetadataValue *value;
1032 value = g_new0 (MetadataValue, 1);
1034 value->default_value = g_strdup (default_metadata);
1035 value->value.string = g_strdup (metadata);
1037 return value;
1040 static MetadataValue *
1041 metadata_value_new_list (GList *metadata)
1043 MetadataValue *value;
1045 value = g_new0 (MetadataValue, 1);
1047 value->is_list = TRUE;
1048 value->value.string_list = eel_g_str_list_copy (metadata);
1050 return value;
1053 static void
1054 metadata_value_destroy (MetadataValue *value)
1056 if (value == NULL) {
1057 return;
1060 if (!value->is_list) {
1061 g_free (value->value.string);
1062 } else {
1063 eel_g_list_free_deep (value->value.string_list);
1065 g_free (value->default_value);
1066 g_free (value);
1069 static gboolean
1070 metadata_value_equal (const MetadataValue *value_a,
1071 const MetadataValue *value_b)
1073 if (value_a->is_list != value_b->is_list) {
1074 return FALSE;
1077 if (!value_a->is_list) {
1078 return eel_strcmp (value_a->value.string,
1079 value_b->value.string) == 0
1080 && eel_strcmp (value_a->default_value,
1081 value_b->default_value) == 0;
1082 } else {
1083 g_assert (value_a->default_value == NULL);
1084 g_assert (value_b->default_value == NULL);
1086 return eel_g_str_list_equal
1087 (value_a->value.string_list,
1088 value_b->value.string_list);
1092 static gboolean
1093 set_metadata_in_metafile (NautilusMetafile *metafile,
1094 const char *file_name,
1095 const char *key,
1096 const char *subkey,
1097 const MetadataValue *value)
1099 gboolean changed;
1101 if (!value->is_list) {
1102 g_assert (subkey == NULL);
1103 changed = set_metadata_string_in_metafile
1104 (metafile, file_name, key,
1105 value->default_value,
1106 value->value.string);
1107 } else {
1108 g_assert (value->default_value == NULL);
1109 changed = set_metadata_list_in_metafile
1110 (metafile, file_name, key, subkey,
1111 value->value.string_list);
1114 return changed;
1117 static char *
1118 get_metadata_string_from_table (NautilusMetafile *metafile,
1119 const char *file_name,
1120 const char *key,
1121 const char *default_metadata)
1123 GHashTable *directory_table, *file_table;
1124 MetadataValue *value;
1126 /* Get the value from the hash table. */
1127 directory_table = metafile->changes;
1128 file_table = directory_table == NULL ? NULL
1129 : g_hash_table_lookup (directory_table, file_name);
1130 value = file_table == NULL ? NULL
1131 : g_hash_table_lookup (file_table, key);
1132 if (value == NULL) {
1133 return g_strdup (default_metadata);
1136 /* Convert it to a string. */
1137 g_assert (!value->is_list);
1138 if (eel_strcmp (value->value.string, value->default_value) == 0) {
1139 return g_strdup (default_metadata);
1141 return g_strdup (value->value.string);
1144 static GList *
1145 get_metadata_list_from_table (NautilusMetafile *metafile,
1146 const char *file_name,
1147 const char *key,
1148 const char *subkey)
1150 GHashTable *directory_table, *file_table;
1151 char *combined_key;
1152 MetadataValue *value;
1154 /* Get the value from the hash table. */
1155 directory_table = metafile->changes;
1156 file_table = directory_table == NULL ? NULL
1157 : g_hash_table_lookup (directory_table, file_name);
1158 if (file_table == NULL) {
1159 return NULL;
1161 combined_key = g_strconcat (key, "/", subkey, NULL);
1162 value = g_hash_table_lookup (file_table, combined_key);
1163 g_free (combined_key);
1164 if (value == NULL) {
1165 return NULL;
1168 /* Copy the list and return it. */
1169 g_assert (value->is_list);
1170 return eel_g_str_list_copy (value->value.string_list);
1173 static guint
1174 str_or_null_hash (gconstpointer str)
1176 return str == NULL ? 0 : g_str_hash (str);
1179 static gboolean
1180 str_or_null_equal (gconstpointer str_a, gconstpointer str_b)
1182 if (str_a == NULL) {
1183 return str_b == NULL;
1185 if (str_b == NULL) {
1186 return FALSE;
1188 return g_str_equal (str_a, str_b);
1191 static gboolean
1192 set_metadata_eat_value (NautilusMetafile *metafile,
1193 const char *file_name,
1194 const char *key,
1195 const char *subkey,
1196 MetadataValue *value)
1198 GHashTable *directory_table, *file_table;
1199 gboolean changed;
1200 char *combined_key;
1201 MetadataValue *old_value;
1203 if (metafile->is_read) {
1204 changed = set_metadata_in_metafile
1205 (metafile, file_name, key, subkey, value);
1206 metadata_value_destroy (value);
1207 } else {
1208 /* Create hash table only when we need it.
1209 * We'll destroy it when we finish reading the metafile.
1211 directory_table = metafile->changes;
1212 if (directory_table == NULL) {
1213 directory_table = g_hash_table_new
1214 (str_or_null_hash, str_or_null_equal);
1215 metafile->changes = directory_table;
1217 file_table = g_hash_table_lookup (directory_table, file_name);
1218 if (file_table == NULL) {
1219 file_table = g_hash_table_new (g_str_hash, g_str_equal);
1220 g_hash_table_insert (directory_table,
1221 g_strdup (file_name), file_table);
1224 /* Find the entry in the hash table. */
1225 if (subkey == NULL) {
1226 combined_key = g_strdup (key);
1227 } else {
1228 combined_key = g_strconcat (key, "/", subkey, NULL);
1230 old_value = g_hash_table_lookup (file_table, combined_key);
1232 /* Put the change into the hash. Delete the old change. */
1233 changed = old_value == NULL || !metadata_value_equal (old_value, value);
1234 if (changed) {
1235 g_hash_table_insert (file_table, combined_key, value);
1236 if (old_value != NULL) {
1237 /* The hash table keeps the old key. */
1238 g_free (combined_key);
1239 metadata_value_destroy (old_value);
1241 } else {
1242 g_free (combined_key);
1243 metadata_value_destroy (value);
1247 return changed;
1250 static void
1251 free_file_table_entry (gpointer key, gpointer value, gpointer user_data)
1253 g_assert (user_data == NULL);
1255 g_free (key);
1256 metadata_value_destroy (value);
1259 static void
1260 free_directory_table_entry (gpointer key, gpointer value, gpointer user_data)
1262 g_assert (user_data == NULL);
1263 g_assert (value != NULL);
1265 g_free (key);
1266 g_hash_table_foreach (value, free_file_table_entry, NULL);
1267 g_hash_table_destroy (value);
1270 static void
1271 destroy_metadata_changes_hash_table (GHashTable *directory_table)
1273 if (directory_table == NULL) {
1274 return;
1276 g_hash_table_foreach (directory_table, free_directory_table_entry, NULL);
1277 g_hash_table_destroy (directory_table);
1280 static void
1281 destroy_xml_string_key (gpointer key, gpointer value, gpointer user_data)
1283 g_assert (key != NULL);
1284 g_assert (user_data == NULL);
1285 g_assert (value != NULL);
1287 xmlFree (key);
1290 static void
1291 metafile_free_metadata (NautilusMetafile *metafile)
1293 g_return_if_fail (NAUTILUS_IS_METAFILE (metafile));
1295 g_hash_table_foreach (metafile->node_hash,
1296 destroy_xml_string_key, NULL);
1297 xmlFreeDoc (metafile->xml);
1298 destroy_metadata_changes_hash_table (metafile->changes);
1301 static char *
1302 get_file_metadata (NautilusMetafile *metafile,
1303 const char *file_name,
1304 const char *key,
1305 const char *default_metadata)
1307 g_return_val_if_fail (NAUTILUS_IS_METAFILE (metafile), NULL);
1308 g_return_val_if_fail (!eel_str_is_empty (file_name), NULL);
1309 g_return_val_if_fail (!eel_str_is_empty (key), NULL);
1311 if (metafile->is_read) {
1312 return get_metadata_string_from_metafile
1313 (metafile, file_name, key, default_metadata);
1314 } else {
1315 return get_metadata_string_from_table
1316 (metafile, file_name, key, default_metadata);
1320 static GList *
1321 get_file_metadata_list (NautilusMetafile *metafile,
1322 const char *file_name,
1323 const char *list_key,
1324 const char *list_subkey)
1326 g_return_val_if_fail (NAUTILUS_IS_METAFILE (metafile), NULL);
1327 g_return_val_if_fail (!eel_str_is_empty (file_name), NULL);
1328 g_return_val_if_fail (!eel_str_is_empty (list_key), NULL);
1329 g_return_val_if_fail (!eel_str_is_empty (list_subkey), NULL);
1331 if (metafile->is_read) {
1332 return get_metadata_list_from_metafile
1333 (metafile, file_name, list_key, list_subkey);
1334 } else {
1335 return get_metadata_list_from_table
1336 (metafile, file_name, list_key, list_subkey);
1340 static gboolean
1341 set_file_metadata (NautilusMetafile *metafile,
1342 const char *file_name,
1343 const char *key,
1344 const char *default_metadata,
1345 const char *metadata)
1347 MetadataValue *value;
1349 g_return_val_if_fail (NAUTILUS_IS_METAFILE (metafile), FALSE);
1350 g_return_val_if_fail (!eel_str_is_empty (file_name), FALSE);
1351 g_return_val_if_fail (!eel_str_is_empty (key), FALSE);
1353 if (metafile->is_read) {
1354 return set_metadata_string_in_metafile (metafile, file_name, key,
1355 default_metadata, metadata);
1356 } else {
1357 value = metadata_value_new (default_metadata, metadata);
1358 return set_metadata_eat_value (metafile, file_name,
1359 key, NULL, value);
1363 static gboolean
1364 set_file_metadata_list (NautilusMetafile *metafile,
1365 const char *file_name,
1366 const char *list_key,
1367 const char *list_subkey,
1368 GList *list)
1370 MetadataValue *value;
1372 g_return_val_if_fail (NAUTILUS_IS_METAFILE (metafile), FALSE);
1373 g_return_val_if_fail (!eel_str_is_empty (file_name), FALSE);
1374 g_return_val_if_fail (!eel_str_is_empty (list_key), FALSE);
1375 g_return_val_if_fail (!eel_str_is_empty (list_subkey), FALSE);
1377 if (metafile->is_read) {
1378 return set_metadata_list_in_metafile (metafile, file_name,
1379 list_key, list_subkey, list);
1380 } else {
1381 value = metadata_value_new_list (list);
1382 return set_metadata_eat_value (metafile, file_name,
1383 list_key, list_subkey, value);
1387 static char *
1388 metafile_get_file_uri (NautilusMetafile *metafile,
1389 const char *file_name)
1391 char *escaped_file_name, *uri;
1393 g_return_val_if_fail (NAUTILUS_IS_METAFILE (metafile), NULL);
1394 g_return_val_if_fail (file_name != NULL, NULL);
1396 escaped_file_name = g_uri_escape_string (file_name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
1398 uri = g_build_filename (metafile->directory_uri, escaped_file_name, NULL);
1399 g_free (escaped_file_name);
1400 return uri;
1403 static void
1404 rename_file_metadata (NautilusMetafile *metafile,
1405 const char *old_file_name,
1406 const char *new_file_name)
1408 gboolean found;
1409 gpointer key, value;
1410 xmlNode *file_node;
1411 GHashTable *hash;
1412 char *old_file_uri, *new_file_uri;
1413 char *escaped;
1415 g_return_if_fail (NAUTILUS_IS_METAFILE (metafile));
1416 g_return_if_fail (old_file_name != NULL);
1417 g_return_if_fail (new_file_name != NULL);
1419 remove_file_metadata (metafile, new_file_name);
1421 if (metafile->is_read) {
1422 /* Move data in XML document if present. */
1423 hash = metafile->node_hash;
1424 found = g_hash_table_lookup_extended
1425 (hash, old_file_name, &key, &value);
1426 if (found) {
1427 g_assert (strcmp ((const char *) key, old_file_name) == 0);
1428 file_node = value;
1429 g_hash_table_remove (hash,
1430 old_file_name);
1431 xmlFree (key);
1432 g_hash_table_insert (hash,
1433 xmlMemStrdup (new_file_name), value);
1434 escaped = g_uri_escape_string (new_file_name, NULL, FALSE);
1435 xmlSetProp (file_node, "name", escaped);
1436 g_free (escaped);
1437 directory_request_write_metafile (metafile);
1439 } else {
1440 /* Move data in hash table. */
1441 /* FIXME: If there's data for this file in the
1442 * metafile on disk, this doesn't arrange for that
1443 * data to be moved to the new name.
1445 hash = metafile->changes;
1446 found = g_hash_table_lookup_extended
1447 (hash, old_file_name, &key, &value);
1448 if (found) {
1449 g_hash_table_remove (hash, old_file_name);
1450 g_free (key);
1451 g_hash_table_insert (hash, g_strdup (new_file_name), value);
1455 /* Rename the thumbnails for the file, if any. */
1456 old_file_uri = metafile_get_file_uri (metafile, old_file_name);
1457 new_file_uri = metafile_get_file_uri (metafile, new_file_name);
1458 nautilus_update_thumbnail_file_renamed (old_file_uri, new_file_uri);
1459 g_free (old_file_uri);
1460 g_free (new_file_uri);
1463 typedef struct {
1464 NautilusMetafile *metafile;
1465 const char *file_name;
1466 } ChangeContext;
1468 static void
1469 apply_one_change (gpointer key, gpointer value, gpointer callback_data)
1471 ChangeContext *context;
1472 const char *hash_table_key, *separator, *metadata_key, *subkey;
1473 char *key_prefix;
1475 g_assert (key != NULL);
1476 g_assert (value != NULL);
1477 g_assert (callback_data != NULL);
1479 context = callback_data;
1481 /* Break the key in half. */
1482 hash_table_key = key;
1483 separator = strchr (hash_table_key, '/');
1484 if (separator == NULL) {
1485 key_prefix = NULL;
1486 metadata_key = hash_table_key;
1487 subkey = NULL;
1488 } else {
1489 key_prefix = g_strndup (hash_table_key, separator - hash_table_key);
1490 metadata_key = key_prefix;
1491 subkey = separator + 1;
1494 /* Set the metadata. */
1495 set_metadata_in_metafile (context->metafile, context->file_name,
1496 metadata_key, subkey, value);
1497 g_free (key_prefix);
1500 static void
1501 apply_file_changes (NautilusMetafile *metafile,
1502 const char *file_name,
1503 GHashTable *changes)
1505 ChangeContext context;
1507 g_assert (NAUTILUS_IS_METAFILE (metafile));
1508 g_assert (file_name != NULL);
1509 g_assert (changes != NULL);
1511 context.metafile = metafile;
1512 context.file_name = file_name;
1514 g_hash_table_foreach (changes, apply_one_change, &context);
1517 static void
1518 apply_one_file_changes (gpointer key, gpointer value, gpointer callback_data)
1520 apply_file_changes (callback_data, key, value);
1521 g_hash_table_destroy (value);
1524 static void
1525 nautilus_metafile_apply_pending_changes (NautilusMetafile *metafile)
1527 if (metafile->changes == NULL) {
1528 return;
1530 g_hash_table_foreach (metafile->changes,
1531 apply_one_file_changes, metafile);
1532 g_hash_table_destroy (metafile->changes);
1533 metafile->changes = NULL;
1536 static void
1537 real_copy_file_metadata (NautilusMetafile *source_metafile,
1538 const char *source_file_name,
1539 NautilusMetafile *destination_metafile,
1540 const char *destination_file_name)
1542 xmlNodePtr source_node, node, root;
1543 GHashTable *hash, *changes;
1544 char *escaped;
1546 real_remove_file_metadata (destination_metafile, destination_file_name);
1547 g_assert (get_file_node (destination_metafile, destination_file_name, FALSE) == NULL);
1549 source_node = get_file_node (source_metafile, source_file_name, FALSE);
1550 if (source_node != NULL) {
1551 node = xmlCopyNode (source_node, TRUE);
1552 root = create_metafile_root (destination_metafile);
1553 xmlAddChild (root, node);
1554 escaped = g_uri_escape_string (destination_file_name, NULL, FALSE);
1555 xmlSetProp (node, "name", escaped);
1556 g_free (escaped);
1557 set_file_node_timestamp (node);
1558 g_hash_table_insert (destination_metafile->node_hash,
1559 xmlMemStrdup (destination_file_name), node);
1560 directory_request_write_metafile (destination_metafile);
1563 hash = source_metafile->changes;
1564 if (hash != NULL) {
1565 changes = g_hash_table_lookup (hash, source_file_name);
1566 if (changes != NULL) {
1567 apply_file_changes (destination_metafile,
1568 destination_file_name,
1569 changes);
1574 static void
1575 copy_file_metadata (NautilusMetafile *source_metafile,
1576 const char *source_file_name,
1577 NautilusMetafile *destination_metafile,
1578 const char *destination_file_name)
1580 char *source_file_uri;
1581 char *destination_file_uri;
1583 g_return_if_fail (NAUTILUS_IS_METAFILE (source_metafile));
1584 g_return_if_fail (source_file_name != NULL);
1585 g_return_if_fail (NAUTILUS_IS_METAFILE (destination_metafile));
1586 g_return_if_fail (destination_file_name != NULL);
1588 if (source_metafile->is_read
1589 && destination_metafile->is_read) {
1590 real_copy_file_metadata (source_metafile,
1591 source_file_name,
1592 destination_metafile,
1593 destination_file_name);
1594 } else {
1595 nautilus_metadata_schedule_copy (source_metafile,
1596 source_file_name,
1597 destination_metafile,
1598 destination_file_name);
1601 /* Copy the thumbnail for the file, if any. */
1602 source_file_uri = metafile_get_file_uri (source_metafile, source_file_name);
1603 destination_file_uri = metafile_get_file_uri (destination_metafile, destination_file_name);
1604 nautilus_update_thumbnail_file_copied (source_file_uri, destination_file_uri);
1605 g_free (source_file_uri);
1606 g_free (destination_file_uri);
1609 static void
1610 real_remove_file_metadata (NautilusMetafile *metafile,
1611 const char *file_name)
1613 gboolean found;
1614 gpointer key, value;
1615 xmlNode *file_node;
1616 GHashTable *hash;
1618 g_return_if_fail (NAUTILUS_IS_METAFILE (metafile));
1619 g_return_if_fail (file_name != NULL);
1621 if (metafile->is_read) {
1622 /* Remove data in XML document if present. */
1623 hash = metafile->node_hash;
1624 found = g_hash_table_lookup_extended
1625 (hash, file_name, &key, &value);
1626 if (found) {
1627 g_assert (strcmp ((const char *) key, file_name) == 0);
1628 file_node = value;
1629 g_hash_table_remove (hash,
1630 file_name);
1631 xmlFree (key);
1632 xmlUnlinkNode (file_node);
1633 xmlFreeNode (file_node);
1634 directory_request_write_metafile (metafile);
1636 } else {
1637 /* Remove data from hash table. */
1638 /* FIXME: If there's data for this file on the
1639 * metafile on disk, this does not arrange for it to
1640 * be removed when the metafile is later read.
1642 hash = metafile->changes;
1643 if (hash != NULL) {
1644 found = g_hash_table_lookup_extended
1645 (hash, file_name, &key, &value);
1646 if (found) {
1647 g_hash_table_remove (hash, file_name);
1648 g_free (key);
1649 destroy_metadata_changes_hash_table (value);
1655 static void
1656 remove_file_metadata (NautilusMetafile *metafile,
1657 const char *file_name)
1659 char *file_uri;
1661 g_return_if_fail (NAUTILUS_IS_METAFILE (metafile));
1662 g_return_if_fail (file_name != NULL);
1664 if (nautilus_metadata_has_scheduled_copy (metafile, file_name)) {
1665 nautilus_metadata_schedule_removal (metafile, file_name);
1666 } else {
1667 real_remove_file_metadata (metafile, file_name);
1670 /* Delete the thumbnails for the file, if any. */
1671 file_uri = metafile_get_file_uri (metafile, file_name);
1672 nautilus_remove_thumbnail_for_file (file_uri);
1673 g_free (file_uri);
1676 static void
1677 set_metafile_contents (NautilusMetafile *metafile,
1678 xmlDocPtr metafile_contents)
1680 GHashTable *hash;
1681 xmlNodePtr node;
1682 xmlChar *name;
1683 char *unescaped_name;
1685 g_return_if_fail (NAUTILUS_IS_METAFILE (metafile));
1686 g_return_if_fail (metafile->xml == NULL);
1688 if (metafile_contents == NULL) {
1689 return;
1692 metafile->xml = metafile_contents;
1694 /* Populate the node hash table. */
1695 hash = metafile->node_hash;
1696 for (node = eel_xml_get_root_children (metafile_contents);
1697 node != NULL; node = node->next) {
1698 if (strcmp (node->name, "file") == 0) {
1699 name = xmlGetProp (node, "name");
1700 unescaped_name = g_uri_unescape_string (name, "/");
1701 if (unescaped_name == NULL ||
1702 g_hash_table_lookup (hash, unescaped_name) != NULL) {
1703 xmlFree (name);
1704 /* FIXME: Should we delete duplicate nodes as we discover them? */
1705 } else {
1706 g_hash_table_insert (hash, unescaped_name, node);
1712 static void
1713 metafile_read_cancel (NautilusMetafile *metafile)
1715 if (metafile->read_state != NULL) {
1716 g_cancellable_cancel (metafile->read_state->cancellable);
1717 metafile->read_state->metafile = NULL;
1718 metafile->read_state = NULL;
1722 static void
1723 metafile_read_state_free (MetafileReadState *state)
1725 g_object_unref (state->cancellable);
1726 g_free (state);
1729 static void
1730 metafile_read_mark_done (NautilusMetafile *metafile, gboolean callback_in_idle)
1732 metafile_read_state_free (metafile->read_state);
1733 metafile->read_state = NULL;
1735 metafile->is_read = TRUE;
1737 /* Move over the changes to the metafile that were in the hash table. */
1738 nautilus_metafile_apply_pending_changes (metafile);
1740 /* Tell change-watchers that we have update information. */
1741 nautilus_metafile_notify_metafile_ready (metafile, callback_in_idle);
1743 async_read_done (metafile);
1746 static void
1747 metafile_read_done_callback (GObject *source_object,
1748 GAsyncResult *res,
1749 gpointer user_data)
1751 MetafileReadState *state;
1752 NautilusMetafile *metafile;
1753 gsize file_size;
1754 char *file_contents;
1756 state = user_data;
1758 if (state->metafile == NULL) {
1759 /* Operation was cancelled. Bail out */
1760 metafile_read_state_free (state);
1761 return;
1764 metafile = state->metafile;
1765 g_assert (metafile->xml == NULL);
1767 if (g_file_load_contents_finish (G_FILE (source_object),
1768 res,
1769 &file_contents, &file_size,
1770 NULL, NULL)) {
1771 set_metafile_contents (metafile, xmlParseMemory (file_contents, file_size));
1772 g_free (file_contents);
1775 metafile_read_mark_done (metafile, FALSE);
1777 nautilus_metadata_process_ready_copies ();
1778 nautilus_metadata_process_ready_removals ();
1781 static void
1782 metafile_read_restart (NautilusMetafile *metafile)
1784 GFile *location;
1785 MetafileReadState *state;
1787 state = g_new0 (MetafileReadState, 1);
1788 state->metafile = metafile;
1789 state->cancellable = g_cancellable_new ();
1791 metafile->read_state = state;
1793 location = g_file_new_for_uri (metafile->private_uri);
1795 g_file_load_contents_async (location, state->cancellable,
1796 metafile_read_done_callback, state);
1798 g_object_unref (location);
1801 static gboolean
1802 allow_metafile (NautilusMetafile *metafile)
1804 const char *uri;
1806 g_assert (NAUTILUS_IS_METAFILE (metafile));
1808 /* Note that this inhibits both reading and writing metadata
1809 * completely. In the future we may want to inhibit writing to
1810 * the real directory while allowing parallel-directory
1811 * metadata.
1814 /* For now, hard-code these schemes. Perhaps we should
1815 * hardcode the schemes that are good for metadata instead of
1816 * the schemes that are bad for it.
1818 /* FIXME bugzilla.gnome.org 42434:
1819 * We need to handle this in a better way. Perhaps a
1820 * better way can wait until we have support for metadata
1821 * access inside gnome-vfs.
1823 uri = metafile->directory_uri;
1824 if (eel_uri_is_search (uri) ||
1825 eel_istr_has_prefix (uri, "gnome-help:") ||
1826 eel_istr_has_prefix (uri, "help:")
1828 return FALSE;
1831 return TRUE;
1834 static void
1835 metafile_read_start (NautilusMetafile *metafile)
1837 g_assert (NAUTILUS_IS_METAFILE (metafile));
1839 if (metafile->is_read ||
1840 metafile->read_state != NULL) {
1841 return;
1844 if (!allow_metafile (metafile)) {
1845 metafile_read_mark_done (metafile, TRUE);
1846 } else {
1847 metafile_read_restart (metafile);
1851 static void
1852 metafile_write_done (NautilusMetafile *metafile)
1854 if (metafile->write_state->write_again) {
1855 metafile_write_start (metafile);
1856 return;
1859 xmlFree (metafile->write_state->buffer);
1860 g_free (metafile->write_state);
1861 metafile->write_state = NULL;
1862 g_object_unref (metafile);
1865 static void
1866 metafile_write_failed (NautilusMetafile *metafile)
1868 metafile_write_done (metafile);
1871 static void
1872 metafile_write_succeeded (NautilusMetafile *metafile)
1874 metafile_write_done (metafile);
1877 static int
1878 write_all (int fd, const char *buffer, int size)
1880 int size_remaining;
1881 const char *p;
1882 ssize_t result;
1884 p = buffer;
1885 size_remaining = size;
1886 while (size_remaining != 0) {
1887 result = write (fd, p, size_remaining);
1888 if (result <= 0 || result > size_remaining) {
1889 return -1;
1891 p += result;
1892 size_remaining -= result;
1895 return size;
1898 static void
1899 metafile_write_local (NautilusMetafile *metafile,
1900 const char *metafile_path)
1902 char *temp_path;
1903 int fd;
1904 gboolean failed;
1906 /* Do this synchronously, since it's likely to be local. Use
1907 * mkstemp to prevent security exploits by making symbolic
1908 * links named .nautilus-metafile.xml.
1911 temp_path = g_strconcat (metafile_path, "XXXXXX", NULL);
1912 failed = FALSE;
1914 fd = mkstemp (temp_path);
1915 if (fd == -1) {
1916 failed = TRUE;
1918 if (!failed && fchmod (fd, METAFILE_PERMISSIONS) == -1) {
1919 failed = TRUE;
1921 if (!failed && write_all (fd,
1922 metafile->write_state->buffer,
1923 metafile->write_state->size) == -1) {
1924 failed = TRUE;
1926 if (fd != -1 && close (fd) == -1) {
1927 failed = TRUE;
1929 if (failed && fd != -1) {
1930 unlink (temp_path);
1932 if (!failed && rename (temp_path, metafile_path) == -1) {
1933 failed = TRUE;
1935 g_free (temp_path);
1937 if (failed) {
1938 metafile_write_failed (metafile);
1939 } else {
1940 metafile_write_succeeded (metafile);
1944 static void
1945 metafile_write_start (NautilusMetafile *metafile)
1947 const char *metafile_uri;
1948 char *metafile_path;
1950 g_assert (NAUTILUS_IS_METAFILE (metafile));
1952 metafile->write_state->write_again = FALSE;
1954 metafile_uri = metafile->private_uri;
1956 metafile_path = g_filename_from_uri (metafile_uri, NULL, NULL);
1957 g_assert (metafile_path != NULL);
1959 metafile_write_local (metafile, metafile_path);
1960 g_free (metafile_path);
1963 static void
1964 metafile_write (NautilusMetafile *metafile)
1966 int xml_doc_size;
1968 g_assert (NAUTILUS_IS_METAFILE (metafile));
1970 g_object_ref (metafile);
1972 /* If we are already writing, then just remember to do it again. */
1973 if (metafile->write_state != NULL) {
1974 g_object_unref (metafile);
1975 metafile->write_state->write_again = TRUE;
1976 return;
1979 /* Don't write anything if there's nothing to write.
1980 * At some point, we might want to change this to actually delete
1981 * the metafile in this case.
1983 if (metafile->xml == NULL) {
1984 g_object_unref (metafile);
1985 return;
1988 /* Create the write state. */
1989 metafile->write_state = g_new0 (MetafileWriteState, 1);
1990 xmlDocDumpMemory (metafile->xml,
1991 &metafile->write_state->buffer,
1992 &xml_doc_size);
1993 metafile->write_state->size = xml_doc_size;
1994 metafile_write_start (metafile);
1997 static gboolean
1998 metafile_write_idle_callback (gpointer callback_data)
2000 NautilusMetafile *metafile;
2002 metafile = NAUTILUS_METAFILE (callback_data);
2004 metafile->write_idle_id = 0;
2005 metafile_write (metafile);
2007 g_object_unref (metafile);
2009 return FALSE;
2012 static void
2013 directory_request_write_metafile (NautilusMetafile *metafile)
2015 g_assert (NAUTILUS_IS_METAFILE (metafile));
2017 if (!allow_metafile (metafile)) {
2018 return;
2021 /* Set up an idle task that will write the metafile. */
2022 if (metafile->write_idle_id == 0) {
2023 g_object_ref (metafile);
2024 metafile->write_idle_id =
2025 g_idle_add (metafile_write_idle_callback, metafile);
2029 static void
2030 nautilus_metafile_class_init (NautilusMetafileClass *klass)
2032 G_OBJECT_CLASS (klass)->finalize = finalize;
2034 signals[CHANGED] = g_signal_new ("changed",
2035 NAUTILUS_TYPE_METAFILE,
2036 G_SIGNAL_RUN_LAST,
2038 NULL, NULL,
2039 g_cclosure_marshal_VOID__POINTER,
2040 G_TYPE_NONE, 1, G_TYPE_POINTER);
2041 signals[READY] = g_signal_new ("ready",
2042 NAUTILUS_TYPE_METAFILE,
2043 G_SIGNAL_RUN_LAST,
2045 NULL, NULL,
2046 g_cclosure_marshal_VOID__VOID,
2047 G_TYPE_NONE, 0);