diff options
author | Mike Engber <engber@src.gnome.org> | 2001-02-12 23:20:02 +0000 |
---|---|---|
committer | Mike Engber <engber@src.gnome.org> | 2001-02-12 23:20:02 +0000 |
commit | b2519987017be0ebb84047ebd3b498e5aeffd8c5 (patch) | |
tree | 18794ea75c112ad7d6393843031bef438a2f70e2 /libnautilus-private/nautilus-metafile.c | |
parent | 13acb877815318af2c118fd372b9f7110ff56eca (diff) | |
download | nautilus-b2519987017be0ebb84047ebd3b498e5aeffd8c5.tar.gz |
reviewed by: John Sullivan
reviewed by: John Sullivan
* libnautilus-extensions/nautilus-directory-async.c:
(metafile_read_mark_done), (metafile_read_done_callback):
* libnautilus-extensions/nautilus-directory-metafile.c:
(free_factory), (get_metafile),
(nautilus_directory_get_file_metadata),
(nautilus_directory_get_file_metadata_list),
(nautilus_directory_set_file_metadata),
(nautilus_directory_set_file_metadata_list),
(nautilus_directory_copy_file_metadata),
(nautilus_directory_remove_file_metadata),
(nautilus_directory_rename_file_metadata):
* libnautilus-extensions/nautilus-directory-metafile.h:
* libnautilus-extensions/nautilus-directory.c:
(nautilus_directory_destroy):
* libnautilus-extensions/nautilus-metafile-factory.c:
(free_factory_instance):
* libnautilus-extensions/nautilus-metafile-factory.h:
* libnautilus-extensions/nautilus-metafile-server.idl:
* libnautilus-extensions/nautilus-metafile.c: (destroy),
(corba_get), (corba_get_list), (corba_set), (corba_set_list),
(corba_copy), (corba_remove), (corba_rename),
(get_or_add_directory_monitor_list_entry),
(remove_directory_monitor_list_entry), (find_monitor_link),
(corba_register_monitor), (corba_unregister_monitor),
(get_metadata_from_node), (get_metadata_list_from_node),
(create_metafile_root), (get_file_node),
(get_metadata_string_from_metafile),
(get_metadata_list_from_metafile),
(set_metadata_string_in_metafile), (set_metadata_list_in_metafile),
(metadata_value_new), (metadata_value_new_list),
(metadata_value_destroy), (metadata_value_equal),
(set_metadata_in_metafile), (get_metadata_string_from_table),
(get_metadata_list_from_table), (str_or_null_hash),
(str_or_null_equal), (set_metadata_eat_value),
(free_file_table_entry), (free_directory_table_entry),
(destroy_metadata_changes_hash_table), (destroy_xml_string_key),
(nautilus_metafile_destroy), (get_file_metadata),
(get_file_metadata_list), (set_file_metadata),
(set_file_metadata_list), (rename_file_metadata),
(apply_one_change), (apply_file_changes), (apply_one_file_changes),
(nautilus_metafile_apply_pending_changes), (copy_file_metadata),
(remove_file_metadata), (nautilus_metafile_set_metafile_contents):
* libnautilus-extensions/nautilus-metafile.h:
* src/nautilus-application.h:
Metadata setting/getting is now done via CORBA so that
out of process components stay in sync. bug 5958
Diffstat (limited to 'libnautilus-private/nautilus-metafile.c')
-rw-r--r-- | libnautilus-private/nautilus-metafile.c | 1125 |
1 files changed, 1106 insertions, 19 deletions
diff --git a/libnautilus-private/nautilus-metafile.c b/libnautilus-private/nautilus-metafile.c index 30540881b..2428e2496 100644 --- a/libnautilus-private/nautilus-metafile.c +++ b/libnautilus-private/nautilus-metafile.c @@ -24,9 +24,21 @@ #include "nautilus-metafile.h" #include "nautilus-metafile-server.h" +#include <libnautilus/nautilus-bonobo-workarounds.h> #include <libnautilus-extensions/nautilus-gtk-macros.h> #include <libnautilus-extensions/nautilus-directory.h> -#include <libnautilus/nautilus-bonobo-workarounds.h> + +#include "nautilus-string.h" +#include "nautilus-metadata.h" +#include "nautilus-thumbnails.h" +#include "nautilus-xml-extensions.h" +#include "nautilus-glib-extensions.h" +#include "nautilus-directory-private.h" + +#include <stdlib.h> +#include <gnome-xml/xmlmemory.h> + +#define METAFILE_XML_VERSION "1.0" struct NautilusMetafileDetails { NautilusDirectory *directory; @@ -62,8 +74,8 @@ static CORBA_boolean corba_set_list (PortableServer_Servant servant, CORBA_Environment *ev); static void corba_copy (PortableServer_Servant servant, - const Nautilus_Metafile source_metafile, const CORBA_char *source_file_name, + const Nautilus_URI destination_directory_uri, const CORBA_char *destination_file_name, CORBA_Environment *ev); static void corba_remove (PortableServer_Servant servant, @@ -81,6 +93,34 @@ static void corba_unregister_monitor (PortableServer_Servant servant, const Nautilus_MetafileMonitor monitor, CORBA_Environment *ev); +static char *get_file_metadata (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *default_metadata); +static GList *get_file_metadata_list (NautilusDirectory *directory, + const char *file_name, + const char *list_key, + const char *list_subkey); +static gboolean set_file_metadata (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *default_metadata, + const char *metadata); +static gboolean set_file_metadata_list (NautilusDirectory *directory, + const char *file_name, + const char *list_key, + const char *list_subkey, + GList *list); + +static void rename_file_metadata (NautilusDirectory *directory, + const char *old_file_name, + const char *new_file_name); +static void copy_file_metadata (NautilusDirectory *source_directory, + const char *source_file_name, + NautilusDirectory *destination_directory, + const char *destination_file_name); +static void remove_file_metadata (NautilusDirectory *directory, + const char *file_name); NAUTILUS_DEFINE_CLASS_BOILERPLATE (NautilusMetafile, nautilus_metafile, BONOBO_OBJECT_TYPE) @@ -152,10 +192,13 @@ nautilus_metafile_initialize (NautilusMetafile *metafile) static void destroy (GtkObject *object) { - NautilusMetafile *metafile; + NautilusMetafile *metafile; + NautilusDirectory *directory; - metafile = NAUTILUS_METAFILE (object); - nautilus_directory_unref (metafile->details->directory); + metafile = NAUTILUS_METAFILE (object); + directory = NAUTILUS_DIRECTORY (metafile->details->directory); + + nautilus_directory_unref (directory); g_free (metafile->details); NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, destroy, (object)); @@ -177,8 +220,22 @@ corba_get (PortableServer_Servant servant, const CORBA_char *default_value, CORBA_Environment *ev) { - /* mse-evil */ - return CORBA_OBJECT_NIL; + NautilusMetafile *metafile; + NautilusDirectory *directory; + + char *metadata; + CORBA_char *result; + + metafile = NAUTILUS_METAFILE (bonobo_object_from_servant (servant)); + directory = NAUTILUS_DIRECTORY (metafile->details->directory); + + metadata = get_file_metadata (directory, file_name, key, default_value); + + result = CORBA_string_dup (metadata != NULL ? metadata : ""); + + g_free (metadata); + + return result; } static Nautilus_MetadataList * @@ -188,8 +245,42 @@ corba_get_list (PortableServer_Servant servant, const CORBA_char *list_subkey, CORBA_Environment *ev) { - /* mse-evil */ - return CORBA_OBJECT_NIL; + NautilusMetafile *metafile; + NautilusDirectory *directory; + + GList *metadata_list; + Nautilus_MetadataList *result; + int len; + int buf_pos; + GList *list_ptr; + + metafile = NAUTILUS_METAFILE (bonobo_object_from_servant (servant)); + directory = NAUTILUS_DIRECTORY (metafile->details->directory); + + metadata_list = get_file_metadata_list (directory, file_name, list_key, list_subkey); + + len = g_list_length (metadata_list); + result = Nautilus_MetadataList__alloc (); + result->_maximum = len; + result->_length = len; + result->_buffer = CORBA_sequence_CORBA_string_allocbuf (len); + + /* We allocate our buffer with CORBA calls, so the caller will clean it + * all up if we set release to TRUE. + */ + CORBA_sequence_set_release (result, TRUE); + + buf_pos = 0; + list_ptr = metadata_list; + while (list_ptr != NULL) { + result->_buffer [buf_pos] = CORBA_string_dup (list_ptr->data); + list_ptr = g_list_next (list_ptr); + ++buf_pos; + } + + nautilus_g_list_free_deep (metadata_list); + + return result; } static CORBA_boolean @@ -200,8 +291,24 @@ corba_set (PortableServer_Servant servant, const CORBA_char *metadata, CORBA_Environment *ev) { - /* mse-evil */ - return CORBA_FALSE; + NautilusMetafile *metafile; + NautilusDirectory *directory; + + CORBA_boolean result; + + if (nautilus_str_is_empty (default_value)) { + default_value = NULL; + } + if (nautilus_str_is_empty (metadata)) { + metadata = NULL; + } + + metafile = NAUTILUS_METAFILE (bonobo_object_from_servant (servant)); + directory = NAUTILUS_DIRECTORY (metafile->details->directory); + + result = set_file_metadata (directory, file_name, key, default_value, metadata); + + return result; } static CORBA_boolean @@ -212,18 +319,49 @@ corba_set_list (PortableServer_Servant servant, const Nautilus_MetadataList *list, CORBA_Environment *ev) { - /* mse-evil */ - return CORBA_FALSE; + NautilusMetafile *metafile; + NautilusDirectory *directory; + + GList *metadata_list; + CORBA_boolean result; + CORBA_unsigned_long buf_pos; + + metafile = NAUTILUS_METAFILE (bonobo_object_from_servant (servant)); + directory = NAUTILUS_DIRECTORY (metafile->details->directory); + + metadata_list = NULL; + for (buf_pos = 0; buf_pos < list->_length; ++buf_pos) { + metadata_list = g_list_prepend (metadata_list, list->_buffer [buf_pos]); + } + metadata_list = g_list_reverse (metadata_list); + + result = set_file_metadata_list (directory, file_name, list_key, list_subkey, metadata_list); + + g_list_free (metadata_list); + + return result; } static void corba_copy (PortableServer_Servant servant, - const Nautilus_Metafile source_metafile, const CORBA_char *source_file_name, + const Nautilus_URI destination_directory_uri, const CORBA_char *destination_file_name, CORBA_Environment *ev) { - /* mse-evil */ + NautilusMetafile *source_metafile; + NautilusDirectory *source_directory; + NautilusDirectory *destination_directory; + + source_metafile = NAUTILUS_METAFILE (bonobo_object_from_servant (servant)); + source_directory = NAUTILUS_DIRECTORY (source_metafile->details->directory); + + destination_directory = nautilus_directory_get (destination_directory_uri); + + copy_file_metadata (source_directory, source_file_name, + destination_directory, destination_file_name); + + nautilus_directory_unref (destination_directory); } static void @@ -231,7 +369,13 @@ corba_remove (PortableServer_Servant servant, const CORBA_char *file_name, CORBA_Environment *ev) { - /* mse-evil */ + NautilusMetafile *metafile; + NautilusDirectory *directory; + + metafile = NAUTILUS_METAFILE (bonobo_object_from_servant (servant)); + directory = NAUTILUS_DIRECTORY (metafile->details->directory); + + remove_file_metadata (directory, file_name); } static void @@ -240,7 +384,72 @@ corba_rename (PortableServer_Servant servant, const CORBA_char *new_file_name, CORBA_Environment *ev) { - /* mse-evil */ + NautilusMetafile *metafile; + NautilusDirectory *directory; + + metafile = NAUTILUS_METAFILE (bonobo_object_from_servant (servant)); + directory = NAUTILUS_DIRECTORY (metafile->details->directory); + + rename_file_metadata (directory, old_file_name, new_file_name); +} + +typedef struct { + GList *monitors; + NautilusDirectory *directory; +} DirectoryMonitorListEntry; + +static GHashTable *directory_monitor_lists; + +static DirectoryMonitorListEntry * +get_or_add_directory_monitor_list_entry (NautilusDirectory *directory) +{ + DirectoryMonitorListEntry *entry; + + if (directory_monitor_lists == NULL) { + directory_monitor_lists = nautilus_g_hash_table_new_free_at_exit (g_direct_hash, g_direct_equal, __FILE__ ": metadata monitors"); + } + + entry = g_hash_table_lookup (directory_monitor_lists, directory); + + if (entry == NULL) { + entry = g_new0 (DirectoryMonitorListEntry, 1); + nautilus_directory_ref (directory); + entry->directory = directory; + g_hash_table_insert (directory_monitor_lists, directory, entry); + } + + return entry; +} + +static void +remove_directory_monitor_list_entry (NautilusDirectory *directory) +{ + DirectoryMonitorListEntry *entry; + + entry = g_hash_table_lookup (directory_monitor_lists, directory); + + if (entry != NULL) { + /* This fn only handles removal when there are no monitors left. + * It makes no attempt to free the monitors. + */ + g_return_if_fail (entry->monitors == NULL); + + g_hash_table_remove (directory_monitor_lists, directory); + nautilus_directory_unref (directory); + g_free (entry); + } +} + +static GList * +find_monitor_link (GList *monitor_list, const Nautilus_MetafileMonitor monitor, CORBA_Environment *ev) +{ + while (monitor_list != NULL) { + if (CORBA_Object_is_equivalent (monitor_list->data, monitor, ev)) { + break; + } + monitor_list = g_list_next (monitor_list); + } + return monitor_list; } static void @@ -248,7 +457,18 @@ corba_register_monitor (PortableServer_Servant servant, const Nautilus_MetafileMonitor monitor, CORBA_Environment *ev) { - /* mse-evil */ + NautilusMetafile *metafile; + NautilusDirectory *directory; + DirectoryMonitorListEntry *monitor_list; + + metafile = NAUTILUS_METAFILE (bonobo_object_from_servant (servant)); + directory = NAUTILUS_DIRECTORY (metafile->details->directory); + + monitor_list = get_or_add_directory_monitor_list_entry (directory); + + g_return_if_fail (find_monitor_link (monitor_list->monitors, monitor, ev) == NULL); + + monitor_list->monitors = g_list_prepend (monitor_list->monitors, (gpointer) CORBA_Object_duplicate (monitor, ev)); } static void @@ -256,5 +476,872 @@ corba_unregister_monitor (PortableServer_Servant servant, const Nautilus_MetafileMonitor monitor, CORBA_Environment *ev) { - /* mse-evil */ + NautilusMetafile *metafile; + NautilusDirectory *directory; + DirectoryMonitorListEntry *monitor_list; + GList *monitor_link; + + metafile = NAUTILUS_METAFILE (bonobo_object_from_servant (servant)); + directory = NAUTILUS_DIRECTORY (metafile->details->directory); + + monitor_list = g_hash_table_lookup (directory_monitor_lists, directory); + + g_return_if_fail (monitor_list != NULL); + + monitor_link = find_monitor_link (monitor_list->monitors, monitor, ev); + + g_return_if_fail (monitor_link != NULL); + + monitor_list->monitors = g_list_remove_link (monitor_list->monitors, monitor_link); + + CORBA_Object_release (monitor_link->data, ev); + g_list_free_1 (monitor_link); + + if (monitor_list->monitors == NULL) { + remove_directory_monitor_list_entry (directory); + } +} + +typedef struct { + gboolean is_list; + union { + char *string; + GList *string_list; + } value; + char *default_value; +} MetadataValue; + +static char * +get_metadata_from_node (xmlNode *node, + const char *key, + const char *default_metadata) +{ + xmlChar *property; + char *result; + + g_return_val_if_fail (key != NULL, NULL); + g_return_val_if_fail (key[0] != '\0', NULL); + + property = xmlGetProp (node, key); + if (property == NULL) { + result = g_strdup (default_metadata); + } else { + result = g_strdup (property); + } + xmlFree (property); + + return result; +} + +static GList * +get_metadata_list_from_node (xmlNode *node, + const char *list_key, + const char *list_subkey) +{ + return nautilus_xml_get_property_for_children + (node, list_key, list_subkey); +} + +static xmlNode * +create_metafile_root (NautilusDirectory *directory) +{ + xmlNode *root; + + if (directory->details->metafile == NULL) { + nautilus_metafile_set_metafile_contents (directory, xmlNewDoc (METAFILE_XML_VERSION)); + } + root = xmlDocGetRootElement (directory->details->metafile); + if (root == NULL) { + root = xmlNewDocNode (directory->details->metafile, NULL, "directory", NULL); + xmlDocSetRootElement (directory->details->metafile, root); + } + + return root; +} + +static xmlNode * +get_file_node (NautilusDirectory *directory, + const char *file_name, + gboolean create) +{ + GHashTable *hash; + xmlNode *root, *node; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + + hash = directory->details->metafile_node_hash; + node = g_hash_table_lookup (hash, file_name); + if (node != NULL) { + return node; + } + + if (create) { + root = create_metafile_root (directory); + node = xmlNewChild (root, NULL, "file", NULL); + xmlSetProp (node, "name", file_name); + g_hash_table_insert (hash, xmlMemStrdup (file_name), node); + return node; + } + + return NULL; +} + +static char * +get_metadata_string_from_metafile (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *default_metadata) +{ + xmlNode *node; + + node = get_file_node (directory, file_name, FALSE); + return get_metadata_from_node (node, key, default_metadata); +} + +static GList * +get_metadata_list_from_metafile (NautilusDirectory *directory, + const char *file_name, + const char *list_key, + const char *list_subkey) +{ + xmlNode *node; + + node = get_file_node (directory, file_name, FALSE); + return get_metadata_list_from_node (node, list_key, list_subkey); +} + +static gboolean +set_metadata_string_in_metafile (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *default_metadata, + const char *metadata) +{ + char *old_metadata; + gboolean old_metadata_matches; + const char *value; + xmlNode *node; + xmlAttr *property_node; + + /* If the data in the metafile is already correct, do nothing. */ + old_metadata = get_file_metadata + (directory, file_name, key, default_metadata); + + old_metadata_matches = nautilus_strcmp (old_metadata, metadata) == 0; + g_free (old_metadata); + if (old_metadata_matches) { + return FALSE; + } + + /* Data that matches the default is represented in the tree by + * the lack of an attribute. + */ + if (nautilus_strcmp (default_metadata, metadata) == 0) { + value = NULL; + } else { + value = metadata; + } + + /* Get or create the node. */ + node = get_file_node (directory, file_name, value != NULL); + + /* Add or remove a property node. */ + if (node != NULL) { + property_node = xmlSetProp (node, key, value); + if (value == NULL) { + xmlRemoveProp (property_node); + } + } + + /* Since we changed the tree, arrange for it to be written. */ + nautilus_directory_request_write_metafile (directory); + return TRUE; +} + +static gboolean +set_metadata_list_in_metafile (NautilusDirectory *directory, + const char *file_name, + const char *list_key, + const char *list_subkey, + GList *list) +{ + xmlNode *node, *child, *next; + gboolean changed; + GList *p; + xmlChar *property; + + /* Get or create the node. */ + node = get_file_node (directory, file_name, list != NULL); + + /* Work with the list. */ + changed = FALSE; + if (node == NULL) { + g_assert (list == NULL); + } else { + p = list; + + /* Remove any nodes except the ones we expect. */ + for (child = nautilus_xml_get_children (node); + child != NULL; + child = next) { + + next = child->next; + if (strcmp (child->name, list_key) == 0) { + property = xmlGetProp (child, list_subkey); + if (property != NULL && p != NULL + && strcmp (property, (char *) p->data) == 0) { + p = p->next; + } else { + xmlUnlinkNode (child); + xmlFreeNode (child); + changed = TRUE; + } + xmlFree (property); + } + } + + /* Add any additional nodes needed. */ + for (; p != NULL; p = p->next) { + child = xmlNewChild (node, NULL, list_key, NULL); + xmlSetProp (child, list_subkey, p->data); + changed = TRUE; + } + } + + if (!changed) { + return FALSE; + } + + nautilus_directory_request_write_metafile (directory); + return TRUE; +} + +static MetadataValue * +metadata_value_new (const char *default_metadata, const char *metadata) +{ + MetadataValue *value; + + value = g_new0 (MetadataValue, 1); + + value->default_value = g_strdup (default_metadata); + value->value.string = g_strdup (metadata); + + return value; +} + +static MetadataValue * +metadata_value_new_list (GList *metadata) +{ + MetadataValue *value; + + value = g_new0 (MetadataValue, 1); + + value->is_list = TRUE; + value->value.string_list = nautilus_g_str_list_copy (metadata); + + return value; +} + +static void +metadata_value_destroy (MetadataValue *value) +{ + if (value == NULL) { + return; + } + + if (!value->is_list) { + g_free (value->value.string); + } else { + nautilus_g_list_free_deep (value->value.string_list); + } + g_free (value->default_value); + g_free (value); +} + +static gboolean +metadata_value_equal (const MetadataValue *value_a, + const MetadataValue *value_b) +{ + if (value_a->is_list != value_b->is_list) { + return FALSE; + } + + if (!value_a->is_list) { + return nautilus_strcmp (value_a->value.string, + value_b->value.string) == 0 + && nautilus_strcmp (value_a->default_value, + value_b->default_value) == 0; + } else { + g_assert (value_a->default_value == NULL); + g_assert (value_b->default_value == NULL); + + return nautilus_g_str_list_equal + (value_a->value.string_list, + value_b->value.string_list); + } +} + +static gboolean +set_metadata_in_metafile (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *subkey, + const MetadataValue *value) +{ + gboolean changed; + + if (!value->is_list) { + g_assert (subkey == NULL); + changed = set_metadata_string_in_metafile + (directory, file_name, key, + value->default_value, + value->value.string); + } else { + g_assert (value->default_value == NULL); + changed = set_metadata_list_in_metafile + (directory, file_name, key, subkey, + value->value.string_list); + } + + return changed; +} + +static char * +get_metadata_string_from_table (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *default_metadata) +{ + GHashTable *directory_table, *file_table; + MetadataValue *value; + + /* Get the value from the hash table. */ + directory_table = directory->details->metadata_changes; + file_table = directory_table == NULL ? NULL + : g_hash_table_lookup (directory_table, file_name); + value = file_table == NULL ? NULL + : g_hash_table_lookup (file_table, key); + if (value == NULL) { + return g_strdup (default_metadata); + } + + /* Convert it to a string. */ + g_assert (!value->is_list); + if (nautilus_strcmp (value->value.string, value->default_value) == 0) { + return g_strdup (default_metadata); + } + return g_strdup (value->value.string); +} + +static GList * +get_metadata_list_from_table (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *subkey) +{ + GHashTable *directory_table, *file_table; + char *combined_key; + MetadataValue *value; + + /* Get the value from the hash table. */ + directory_table = directory->details->metadata_changes; + file_table = directory_table == NULL ? NULL + : g_hash_table_lookup (directory_table, file_name); + if (file_table == NULL) { + return NULL; + } + combined_key = g_strconcat (key, "/", subkey, NULL); + value = g_hash_table_lookup (file_table, combined_key); + g_free (combined_key); + if (value == NULL) { + return NULL; + } + + /* Copy the list and return it. */ + g_assert (value->is_list); + return nautilus_g_str_list_copy (value->value.string_list); +} + +static guint +str_or_null_hash (gconstpointer str) +{ + return str == NULL ? 0 : g_str_hash (str); +} + +static gboolean +str_or_null_equal (gconstpointer str_a, gconstpointer str_b) +{ + if (str_a == NULL) { + return str_b == NULL; + } + if (str_b == NULL) { + return FALSE; + } + return g_str_equal (str_a, str_b); +} + +static gboolean +set_metadata_eat_value (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *subkey, + MetadataValue *value) +{ + GHashTable *directory_table, *file_table; + gboolean changed; + char *combined_key; + MetadataValue *old_value; + + if (directory->details->metafile_read) { + changed = set_metadata_in_metafile + (directory, file_name, key, subkey, value); + metadata_value_destroy (value); + } else { + /* Create hash table only when we need it. + * We'll destroy it when we finish reading the metafile. + */ + directory_table = directory->details->metadata_changes; + if (directory_table == NULL) { + directory_table = g_hash_table_new + (str_or_null_hash, str_or_null_equal); + directory->details->metadata_changes = directory_table; + } + file_table = g_hash_table_lookup (directory_table, file_name); + if (file_table == NULL) { + file_table = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (directory_table, + g_strdup (file_name), file_table); + } + + /* Find the entry in the hash table. */ + if (subkey == NULL) { + combined_key = g_strdup (key); + } else { + combined_key = g_strconcat (key, "/", subkey, NULL); + } + old_value = g_hash_table_lookup (file_table, combined_key); + + /* Put the change into the hash. Delete the old change. */ + changed = old_value == NULL || !metadata_value_equal (old_value, value); + if (changed) { + g_hash_table_insert (file_table, combined_key, value); + if (old_value != NULL) { + /* The hash table keeps the old key. */ + g_free (combined_key); + metadata_value_destroy (old_value); + } + } else { + g_free (combined_key); + metadata_value_destroy (value); + } + } + + return changed; +} + +static void +free_file_table_entry (gpointer key, gpointer value, gpointer user_data) +{ + g_assert (user_data == NULL); + + g_free (key); + metadata_value_destroy (value); +} + +static void +free_directory_table_entry (gpointer key, gpointer value, gpointer user_data) +{ + g_assert (user_data == NULL); + g_assert (value != NULL); + + g_free (key); + g_hash_table_foreach (value, free_file_table_entry, NULL); + g_hash_table_destroy (value); +} + +static void +destroy_metadata_changes_hash_table (GHashTable *directory_table) +{ + if (directory_table == NULL) { + return; + } + g_hash_table_foreach (directory_table, free_directory_table_entry, NULL); + g_hash_table_destroy (directory_table); +} + +static void +destroy_xml_string_key (gpointer key, gpointer value, gpointer user_data) +{ + g_assert (key != NULL); + g_assert (user_data == NULL); + g_assert (value != NULL); + + xmlFree (key); +} + +void +nautilus_metafile_destroy (NautilusDirectory *directory) +{ + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + + g_hash_table_foreach (directory->details->metafile_node_hash, + destroy_xml_string_key, NULL); + xmlFreeDoc (directory->details->metafile); + destroy_metadata_changes_hash_table (directory->details->metadata_changes); +} + +static char * +get_file_metadata (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *default_metadata) +{ + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); + g_return_val_if_fail (!nautilus_str_is_empty (file_name), NULL); + g_return_val_if_fail (!nautilus_str_is_empty (key), NULL); + + if (directory->details->metafile_read) { + return get_metadata_string_from_metafile + (directory, file_name, key, default_metadata); + } else { + return get_metadata_string_from_table + (directory, file_name, key, default_metadata); + } +} + +static GList * +get_file_metadata_list (NautilusDirectory *directory, + const char *file_name, + const char *list_key, + const char *list_subkey) +{ + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); + g_return_val_if_fail (!nautilus_str_is_empty (file_name), NULL); + g_return_val_if_fail (!nautilus_str_is_empty (list_key), NULL); + g_return_val_if_fail (!nautilus_str_is_empty (list_subkey), NULL); + + if (directory->details->metafile_read) { + return get_metadata_list_from_metafile + (directory, file_name, list_key, list_subkey); + } else { + return get_metadata_list_from_table + (directory, file_name, list_key, list_subkey); + } +} + +static gboolean +set_file_metadata (NautilusDirectory *directory, + const char *file_name, + const char *key, + const char *default_metadata, + const char *metadata) +{ + MetadataValue *value; + + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE); + g_return_val_if_fail (!nautilus_str_is_empty (file_name), FALSE); + g_return_val_if_fail (!nautilus_str_is_empty (key), FALSE); + + if (directory->details->metafile_read) { + return set_metadata_string_in_metafile (directory, file_name, key, + default_metadata, metadata); + } else { + value = metadata_value_new (default_metadata, metadata); + return set_metadata_eat_value (directory, file_name, + key, NULL, value); + } +} + +static gboolean +set_file_metadata_list (NautilusDirectory *directory, + const char *file_name, + const char *list_key, + const char *list_subkey, + GList *list) +{ + MetadataValue *value; + + g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), FALSE); + g_return_val_if_fail (!nautilus_str_is_empty (file_name), FALSE); + g_return_val_if_fail (!nautilus_str_is_empty (list_key), FALSE); + g_return_val_if_fail (!nautilus_str_is_empty (list_subkey), FALSE); + + if (directory->details->metafile_read) { + return set_metadata_list_in_metafile (directory, file_name, + list_key, list_subkey, list); + } else { + value = metadata_value_new_list (list); + return set_metadata_eat_value (directory, file_name, + list_key, list_subkey, value); + } +} + +static void +rename_file_metadata (NautilusDirectory *directory, + const char *old_file_name, + const char *new_file_name) +{ + gboolean found; + gpointer key, value; + xmlNode *file_node; + GHashTable *hash; + char *old_file_uri, *new_file_uri; + + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + g_return_if_fail (old_file_name != NULL); + g_return_if_fail (new_file_name != NULL); + + remove_file_metadata (directory, new_file_name); + + if (directory->details->metafile_read) { + /* Move data in XML document if present. */ + hash = directory->details->metafile_node_hash; + found = g_hash_table_lookup_extended + (hash, old_file_name, &key, &value); + if (found) { + g_assert (strcmp ((const char *) key, old_file_name) == 0); + file_node = value; + g_hash_table_remove (hash, + old_file_name); + xmlFree (key); + g_hash_table_insert (hash, + xmlMemStrdup (new_file_name), value); + xmlSetProp (file_node, "name", new_file_name); + nautilus_directory_request_write_metafile (directory); + } + } else { + /* Move data in hash table. */ + /* FIXME: If there's data for this file in the + * metafile on disk, this doesn't arrange for that + * data to be moved to the new name. + */ + hash = directory->details->metadata_changes; + found = g_hash_table_lookup_extended + (hash, old_file_name, &key, &value); + if (found) { + g_hash_table_remove (hash, old_file_name); + g_free (key); + g_hash_table_insert (hash, g_strdup (new_file_name), value); + } + } + + /* Rename the thumbnails for the file, if any. */ + old_file_uri = nautilus_directory_get_file_uri (directory, old_file_name); + new_file_uri = nautilus_directory_get_file_uri (directory, new_file_name); + nautilus_update_thumbnail_file_renamed (old_file_uri, new_file_uri); + g_free (old_file_uri); + g_free (new_file_uri); +} + +typedef struct { + NautilusDirectory *directory; + const char *file_name; +} ChangeContext; + +static void +apply_one_change (gpointer key, gpointer value, gpointer callback_data) +{ + ChangeContext *context; + const char *hash_table_key, *separator, *metadata_key, *subkey; + char *key_prefix; + + g_assert (key != NULL); + g_assert (value != NULL); + g_assert (callback_data != NULL); + + context = callback_data; + + /* Break the key in half. */ + hash_table_key = key; + separator = strchr (hash_table_key, '/'); + if (separator == NULL) { + key_prefix = NULL; + metadata_key = hash_table_key; + subkey = NULL; + } else { + key_prefix = g_strndup (hash_table_key, separator - hash_table_key); + metadata_key = key_prefix; + subkey = separator + 1; + } + + /* Set the metadata. */ + set_metadata_in_metafile (context->directory, context->file_name, + metadata_key, subkey, value); + g_free (key_prefix); +} + +static void +apply_file_changes (NautilusDirectory *directory, + const char *file_name, + GHashTable *changes) +{ + ChangeContext context; + + g_assert (NAUTILUS_IS_DIRECTORY (directory)); + g_assert (file_name != NULL); + g_assert (changes != NULL); + + context.directory = directory; + context.file_name = file_name; + + g_hash_table_foreach (changes, apply_one_change, &context); +} + +static void +apply_one_file_changes (gpointer key, gpointer value, gpointer callback_data) +{ + apply_file_changes (callback_data, key, value); + g_hash_table_destroy (value); +} + +void +nautilus_metafile_apply_pending_changes (NautilusDirectory *directory) +{ + if (directory->details->metadata_changes == NULL) { + return; + } + g_hash_table_foreach (directory->details->metadata_changes, + apply_one_file_changes, directory); + g_hash_table_destroy (directory->details->metadata_changes); + directory->details->metadata_changes = NULL; +} + +static void +copy_file_metadata (NautilusDirectory *source_directory, + const char *source_file_name, + NautilusDirectory *destination_directory, + const char *destination_file_name) +{ + xmlNodePtr source_node, node, root; + GHashTable *hash, *changes; + + g_return_if_fail (NAUTILUS_IS_DIRECTORY (source_directory)); + g_return_if_fail (source_file_name != NULL); + g_return_if_fail (NAUTILUS_IS_DIRECTORY (destination_directory)); + g_return_if_fail (destination_file_name != NULL); + + /* FIXME bugzilla.eazel.com 3343: This does not properly + * handle the case where we don't have the source metadata yet + * since it's not read in. + */ + + remove_file_metadata + (destination_directory, destination_file_name); + g_assert (get_file_node (destination_directory, destination_file_name, FALSE) == NULL); + + source_node = get_file_node (source_directory, source_file_name, FALSE); + if (source_node != NULL) { + if (destination_directory->details->metafile_read) { + node = xmlCopyNode (source_node, TRUE); + root = create_metafile_root (destination_directory); + xmlAddChild (root, node); + xmlSetProp (node, "name", destination_file_name); + g_hash_table_insert (destination_directory->details->metafile_node_hash, + xmlMemStrdup (destination_file_name), node); + } else { + /* FIXME bugzilla.eazel.com 6526: Copying data into a destination + * where the metafile was not yet read is not implemented. + */ + g_warning ("not copying metadata"); + } + } + + hash = source_directory->details->metadata_changes; + if (hash != NULL) { + changes = g_hash_table_lookup (hash, source_file_name); + if (changes != NULL) { + apply_file_changes (destination_directory, + destination_file_name, + changes); + } + } + + /* FIXME: Do we want to copy the thumbnail here like in the + * rename and remove cases? + */ +} + +static void +remove_file_metadata (NautilusDirectory *directory, + const char *file_name) +{ + gboolean found; + gpointer key, value; + xmlNode *file_node; + GHashTable *hash; + char *file_uri; + + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + g_return_if_fail (file_name != NULL); + + if (directory->details->metafile_read) { + /* Remove data in XML document if present. */ + hash = directory->details->metafile_node_hash; + found = g_hash_table_lookup_extended + (hash, file_name, &key, &value); + if (found) { + g_assert (strcmp ((const char *) key, file_name) == 0); + file_node = value; + g_hash_table_remove (hash, + file_name); + xmlFree (key); + nautilus_xml_remove_node (file_node); + xmlFreeNode (file_node); + nautilus_directory_request_write_metafile (directory); + } + } else { + /* Remove data from hash table. */ + /* FIXME: If there's data for this file on the + * metafile on disk, this does not arrange for it to + * be removed when the metafile is later read. + */ + hash = directory->details->metadata_changes; + if (hash != NULL) { + found = g_hash_table_lookup_extended + (hash, file_name, &key, &value); + if (found) { + g_hash_table_remove (hash, file_name); + g_free (key); + metadata_value_destroy (value); + } + } + } + + /* Delete the thumbnails for the file, if any. */ + file_uri = nautilus_directory_get_file_uri (directory, file_name); + nautilus_remove_thumbnail_for_file (file_uri); + g_free (file_uri); +} + +void +nautilus_metafile_set_metafile_contents (NautilusDirectory *directory, + xmlDocPtr metafile_contents) +{ + GHashTable *hash; + xmlNodePtr node; + xmlChar *name; + + g_return_if_fail (NAUTILUS_IS_DIRECTORY (directory)); + g_return_if_fail (directory->details->metafile == NULL); + + if (metafile_contents == NULL) { + return; + } + + directory->details->metafile = metafile_contents; + + /* Populate the node hash table. */ + hash = directory->details->metafile_node_hash; + for (node = nautilus_xml_get_root_children (metafile_contents); + node != NULL; node = node->next) { + if (strcmp (node->name, "file") == 0) { + name = xmlGetProp (node, "name"); + if (g_hash_table_lookup (hash, name) != NULL) { + xmlFree (name); + /* FIXME: Should we delete duplicate nodes as we discover them? */ + } else { + g_hash_table_insert (hash, name, node); + } + } + } } |