diff --git a/.gitignore b/.gitignore
index c4a6268..bc01ee6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -122,6 +122,7 @@ run-tests
 /gcr/tests/test-gnupg-collection
 /gcr/tests/test-gnupg-key
 /gcr/tests/test-gnupg-process
+/gcr/tests/test-memory-icon
 /gcr/tests/test-parser
 /gcr/tests/test-pkcs11-certificate
 /gcr/tests/test-record
diff --git a/docs/reference/gcr/gcr-sections.txt b/docs/reference/gcr/gcr-sections.txt
index 6f6e16d..84210a2 100644
--- a/docs/reference/gcr/gcr-sections.txt
+++ b/docs/reference/gcr/gcr-sections.txt
@@ -436,6 +436,9 @@ GcrUnlockOptionsWidgetPrivate
 GCR_RECORD_SCHEMA_PUB
 GCR_RECORD_SCHEMA_UID
 GCR_RECORD_SCHEMA_SEC
+GCR_RECORD_SCHEMA_ATTRIBUTE
+GCR_RECORD_SCHEMA_FPR
+GCR_RECORD_SCHEMA_XA1
 GCR_GNUPG_COLLECTION
 GCR_GNUPG_COLLECTION_CLASS
 GCR_GNUPG_COLLECTION_GET_CLASS
@@ -454,6 +457,9 @@ GcrRecordColumns
 GcrRecordPubColumns
 GcrRecordUidColumns
 GcrRecordSecColumns
+GcrRecordAttributeColumns
+GcrRecordFprColumns
+GcrRecordXa1Columns
 GcrRecord
 GCR_GNUPG_PROCESS
 GCR_GNUPG_PROCESS_CLASS
@@ -472,4 +478,14 @@ GcrGnupgKey
 GcrGnupgKeyClass
 GcrGnupgKeyPrivate
 GcrLineCallback
+GCR_IS_MEMORY_ICON
+GCR_IS_MEMORY_ICON_CLASS
+GCR_MEMORY_ICON
+GCR_MEMORY_ICON_CLASS
+GCR_MEMORY_ICON_COLUMNS
+GCR_MEMORY_ICON_GET_CLASS
+GCR_TYPE_MEMORY_ICON
+GcrMemoryIcon
+GcrMemoryIconClass
+GcrMemoryIconPrivate
 </SECTION>
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index 27c01ab..de52066 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -84,6 +84,7 @@ libgcr@GCR_VERSION_SUFFIX@_la_SOURCES = \
 	gcr-gnupg-collection.c gcr-gnupg-collection.h \
 	gcr-gnupg-key.c gcr-gnupg-key.h \
 	gcr-gnupg-process.c gcr-gnupg-process.h \
+	gcr-gnupg-util.c gcr-gnupg-util.h \
 	gcr-icons.c gcr-icons.h \
 	gcr-import-dialog.c gcr-import-dialog.h \
 	gcr-importer.c gcr-importer.h  \
@@ -91,6 +92,7 @@ libgcr@GCR_VERSION_SUFFIX@_la_SOURCES = \
 	gcr-key-renderer.c gcr-key-renderer.h \
 	gcr-key-widget.c gcr-key-widget.h \
 	gcr-library.c gcr-library.h \
+	gcr-memory-icon.c gcr-memory-icon.h \
 	gcr-parser.c gcr-parser.h \
 	gcr-pkcs11-certificate.c gcr-pkcs11-certificate.h \
 	gcr-record.c gcr-record.h \
diff --git a/gcr/gcr-gnupg-collection.c b/gcr/gcr-gnupg-collection.c
index 44abe93..7e9dddb 100644
--- a/gcr/gcr-gnupg-collection.c
+++ b/gcr/gcr-gnupg-collection.c
@@ -30,6 +30,7 @@
 #include "gcr-gnupg-collection.h"
 #include "gcr-gnupg-key.h"
 #include "gcr-gnupg-process.h"
+#include "gcr-gnupg-util.h"
 #include "gcr-internal.h"
 #include "gcr-util.h"
 
@@ -159,7 +160,7 @@ static GList*
 gcr_gnupg_collection_real_get_objects (GcrCollection *coll)
 {
 	GcrGnupgCollection *self = GCR_GNUPG_COLLECTION (coll);
-	return g_hash_table_get_keys (self->pv->items);
+	return g_hash_table_get_values (self->pv->items);
 }
 
 static void
@@ -223,6 +224,10 @@ typedef struct {
 	guint error_sig;
 	guint status_sig;
 	guint attribute_sig;
+
+	GQueue *attribute_queue;              /* Queue of unprocessed GcrRecord* status records */
+	GByteArray *attribute_buf;            /* Buffer of unprocessed attribute data received */
+	GHashTable *attributes;               /* Processed attributes waiting for a matching key */
 } GcrGnupgCollectionLoad;
 
 /* Forward declarations */
@@ -251,6 +256,17 @@ _gcr_gnupg_collection_load_free (gpointer data)
 		g_source_remove (load->attribute_sig);
 	if (load->cancel)
 		g_object_unref (load->cancel);
+
+	if (load->attribute_queue) {
+		while (!g_queue_is_empty (load->attribute_queue))
+			_gcr_record_free (g_queue_pop_head (load->attribute_queue));
+		g_queue_free (load->attribute_queue);
+	}
+	if (load->attribute_buf)
+		g_byte_array_unref (load->attribute_buf);
+	if (load->attributes)
+		g_hash_table_destroy (load->attributes);
+
 	g_slice_free (GcrGnupgCollectionLoad, load);
 }
 
@@ -258,7 +274,29 @@ static void
 process_records_as_public_key (GcrGnupgCollectionLoad *load, GPtrArray *records,
                                const gchar *keyid)
 {
+	GPtrArray *attr_records = NULL;
+	const gchar *fingerprint;
 	GcrGnupgKey *key;
+	guint i;
+
+	/* Add in any attributes we have loaded */
+	fingerprint = _gcr_gnupg_key_get_fingerprint_for_records (records);
+	if (fingerprint && load->attributes)
+		attr_records = g_hash_table_lookup (load->attributes, fingerprint);
+	if (attr_records) {
+		_gcr_debug ("adding %d user id attribute(s) to key/fingerprint: %s/%s",
+		            (gint)attr_records->len, keyid, fingerprint);
+
+		if (!g_hash_table_steal (load->attributes, fingerprint))
+			g_assert_not_reached ();
+
+		/* Move all the attribute records over to main records set */
+		for (i = 0; i < attr_records->len; i++)
+			g_ptr_array_add (records, attr_records->pdata[i]);
+
+		/* Shallow free of attr_records array */
+		g_free (g_ptr_array_free (attr_records, FALSE));
+	}
 
 	/* Note that we've seen this keyid */
 	g_hash_table_remove (load->difference, keyid);
@@ -332,6 +370,76 @@ process_records_as_key (GcrGnupgCollectionLoad *load)
 	g_ptr_array_unref (records);
 }
 
+static gboolean
+process_outstanding_attribute (GcrGnupgCollectionLoad *load, GcrRecord *record)
+{
+	const gchar *fingerprint;
+	GPtrArray *records;
+	GcrRecord *xa1;
+	guint length;
+
+	if (!_gcr_record_get_uint (record, GCR_RECORD_ATTRIBUTE_LENGTH, &length))
+		g_return_val_if_reached (FALSE);
+	fingerprint = _gcr_record_get_raw (record, GCR_RECORD_ATTRIBUTE_FINGERPRINT);
+	g_return_val_if_fail (fingerprint != NULL, FALSE);
+
+	/* Do we have enough data for this attribute? */
+	if (!load->attribute_buf || load->attribute_buf->len < length) {
+		_gcr_debug ("not enough attribute data in buffer: %u", length);
+		return FALSE;
+	}
+
+	if (!load->attributes)
+		load->attributes = g_hash_table_new_full (g_str_hash, g_str_equal,
+							  g_free, (GDestroyNotify)g_ptr_array_unref);
+
+	records = g_hash_table_lookup (load->attributes, fingerprint);
+	if (!records) {
+		records = g_ptr_array_new_with_free_func (_gcr_record_free);
+		g_hash_table_insert (load->attributes, g_strdup (fingerprint), records);
+	}
+
+	_gcr_debug ("new attribute of length %d for key with fingerprint %s",
+		    length, fingerprint);
+
+	xa1 = _gcr_gnupg_build_xa1_record (record, load->attribute_buf->data, length);
+	g_ptr_array_add (records, xa1);
+
+	/* Did we use up all the attribute data? Get rid of the buffer */
+	if (length == load->attribute_buf->len) {
+		g_byte_array_unref (load->attribute_buf);
+		load->attribute_buf = NULL;
+
+	/* Otherwise clear out the used data from buffer */
+	} else {
+		g_byte_array_remove_range (load->attribute_buf, 0, length);
+	}
+
+	return TRUE;
+}
+
+static void
+process_outstanding_attributes (GcrGnupgCollectionLoad *load)
+{
+	GcrRecord *record;
+
+	if (load->attribute_queue == NULL)
+		return;
+
+	_gcr_debug ("%d outstanding attribute records",
+	            (gint)g_queue_get_length (load->attribute_queue));
+
+	for (;;) {
+		record = g_queue_peek_head (load->attribute_queue);
+		if (record == NULL)
+			break;
+		if (!process_outstanding_attribute (load, record))
+			break;
+		g_queue_pop_head (load->attribute_queue);
+		_gcr_record_free (record);
+	}
+}
+
 static void
 on_line_parse_output (const gchar *line, gpointer user_data)
 {
@@ -362,9 +470,10 @@ on_line_parse_output (const gchar *line, gpointer user_data)
 		record = NULL;
 
 	/*
-	 * 'uid' schema lines get added to the key that came before.
+	 * 'uid' and 'fpr' schema lines get added to the key that came before.
 	 */
-	} else if (schema == GCR_RECORD_SCHEMA_UID) {
+	} else if (schema == GCR_RECORD_SCHEMA_UID ||
+	           schema == GCR_RECORD_SCHEMA_FPR) {
 		if (load->records->len) {
 			g_ptr_array_add (load->records, record);
 			record = NULL;
@@ -395,6 +504,41 @@ on_gnupg_process_error_line (GcrGnupgProcess *process, const gchar *line,
 }
 
 static void
+on_gnupg_process_status_record (GcrGnupgProcess *process, GcrRecord *record,
+                                gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+
+	if (GCR_RECORD_SCHEMA_ATTRIBUTE != _gcr_record_get_schema (record))
+		return;
+
+	if (!load->attribute_queue)
+		load->attribute_queue = g_queue_new ();
+
+	g_queue_push_tail (load->attribute_queue, _gcr_record_copy (record));
+	process_outstanding_attributes (load);
+}
+
+static void
+on_gnupg_process_attribute_data (GcrGnupgProcess *process, GByteArray *buffer,
+                                 gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
+
+	/* If we don't have a buffer, just claim this one */
+	if (!load->attribute_buf)
+		load->attribute_buf = g_byte_array_ref (buffer);
+
+	/* If we have data remaining over, add it to our buffer */
+	else
+		g_byte_array_append (load->attribute_buf, buffer->data, buffer->len);
+
+	process_outstanding_attributes (load);
+}
+
+static void
 on_gnupg_process_completed (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
@@ -478,14 +622,9 @@ spawn_gnupg_list_process (GcrGnupgCollectionLoad *load, GSimpleAsyncResult *res)
 
 	g_ptr_array_add (argv, (gpointer)"--fixed-list-mode");
 	g_ptr_array_add (argv, (gpointer)"--with-colons");
+	g_ptr_array_add (argv, (gpointer)"--with-fingerprint");
 	g_ptr_array_add (argv, NULL);
 
-	if (_gcr_debugging) {
-		gchar *command = g_strjoinv (" ", (gchar**)argv->pdata);
-		_gcr_debug ("spawning gnupg process: %s", command);
-		g_free (command);
-	}
-
 	/* res is unreffed in on_gnupg_process_completed */
 	_gcr_gnupg_process_run_async (load->process, (const gchar**)argv->pdata, NULL, flags,
 	                              load->cancel, on_gnupg_process_completed,
@@ -529,10 +668,8 @@ _gcr_gnupg_collection_load_async (GcrGnupgCollection *self, GCancellable *cancel
 	load->process = _gcr_gnupg_process_new (self->pv->directory, NULL);
 	load->output_sig = g_signal_connect (load->process, "output-data", G_CALLBACK (on_gnupg_process_output_data), res);
 	load->error_sig = g_signal_connect (load->process, "error-line", G_CALLBACK (on_gnupg_process_error_line), res);
-#if 0
-	load->status_sig = g_signal_connect (load->process, "status-message", G_CALLBACK (on_gnupg_process_status_message), res);
+	load->status_sig = g_signal_connect (load->process, "status-record", G_CALLBACK (on_gnupg_process_status_record), res);
 	load->attribute_sig = g_signal_connect (load->process, "attribute-data", G_CALLBACK (on_gnupg_process_attribute_data), res);
-#endif
 
 	/*
 	 * Track all the keys we currently have, at end remove those that
diff --git a/gcr/gcr-gnupg-key.c b/gcr/gcr-gnupg-key.c
index 31ab77b..1274ec6 100644
--- a/gcr/gcr-gnupg-key.c
+++ b/gcr/gcr-gnupg-key.c
@@ -21,8 +21,9 @@
 
 #include "config.h"
 
-#include "gcr-record.h"
 #include "gcr-gnupg-key.h"
+#include "gcr-record.h"
+#include "gcr-memory-icon.h"
 
 #include "gck/gck.h"
 
@@ -37,12 +38,14 @@ enum {
 	PROP_LABEL,
 	PROP_MARKUP,
 	PROP_DESCRIPTION,
-	PROP_SHORT_KEYID
+	PROP_SHORT_KEYID,
+	PROP_ICON
 };
 
 struct _GcrGnupgKeyPrivate {
 	GPtrArray *public_records;
 	GPtrArray *secret_records;
+	GIcon *icon;
 };
 
 G_DEFINE_TYPE (GcrGnupgKey, _gcr_gnupg_key, G_TYPE_OBJECT);
@@ -159,6 +162,9 @@ _gcr_gnupg_key_get_property (GObject *obj, guint prop_id, GValue *value,
 	case PROP_SHORT_KEYID:
 		g_value_set_string (value, calculate_short_keyid (self));
 		break;
+	case PROP_ICON:
+		g_value_set_object (value, _gcr_gnupg_key_get_icon (self));
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
 		break;
@@ -240,6 +246,15 @@ _gcr_gnupg_key_class_init (GcrGnupgKeyClass *klass)
 	g_object_class_install_property (gobject_class, PROP_SHORT_KEYID,
 	         g_param_spec_string ("short-keyid", "Short Key ID", "Display key identifier",
 	                              "", G_PARAM_READABLE));
+
+	/**
+	 * GcrGnupgKey::icon:
+	 *
+	 * Icon for this key.
+	 */
+	g_object_class_install_property (gobject_class, PROP_ICON,
+	         g_param_spec_object ("icon", "Icon", "Icon for this key",
+	                              G_TYPE_ICON, G_PARAM_READABLE));
 }
 
 /**
@@ -410,6 +425,77 @@ _gcr_gnupg_key_get_keyid_for_records (GPtrArray *records)
 	return NULL;
 }
 
+const gchar*
+_gcr_gnupg_key_get_fingerprint_for_records (GPtrArray *records)
+{
+	GcrRecord *record;
+
+	record = _gcr_record_find (records, GCR_RECORD_SCHEMA_FPR);
+	if (record != NULL)
+		return _gcr_record_get_raw (record, GCR_RECORD_FPR_FINGERPRINT);
+	return NULL;
+}
+
+#define TYPE_IMAGE 0x01
+#define IMAGE_HEADER_LEN 0x10
+#define IMAGE_JPEG_SIG "\x10\x00\x01\x01"
+#define IMAGE_JPEG_SIG_LEN 4
+
+static GIcon*
+load_user_attribute_icon (GcrGnupgKey *self)
+{
+	GcrRecord *record;
+	guchar *data;
+	gsize n_data;
+	guint type;
+	guint i;
+
+	for (i = 0; i < self->pv->public_records->len; i++) {
+		record = self->pv->public_records->pdata[i];
+		if (GCR_RECORD_SCHEMA_XA1 != _gcr_record_get_schema (record))
+			continue;
+		if (!_gcr_record_get_uint (record, GCR_RECORD_XA1_TYPE, &type))
+			continue;
+		if (type != TYPE_IMAGE)
+			continue;
+
+		/* TODO: Validity? */
+
+		data = _gcr_record_get_base64 (record, GCR_RECORD_XA1_DATA, &n_data);
+		g_return_val_if_fail (data != NULL, NULL);
+
+		/* Header is 16 bytes long */
+		if (n_data <= IMAGE_HEADER_LEN)
+			continue;
+
+		/* These are the header bytes. See gnupg doc/DETAILS */
+		g_assert (IMAGE_JPEG_SIG_LEN < IMAGE_HEADER_LEN);
+		if (memcmp (data, IMAGE_JPEG_SIG, IMAGE_JPEG_SIG_LEN) != 0)
+			continue;
+
+		/* We have a valid header */
+		return G_ICON (_gcr_memory_icon_new_full ("image/jpeg", data,
+		                                          n_data, IMAGE_HEADER_LEN,
+		                                          g_free));
+	}
+
+	return NULL;
+}
+
+GIcon*
+_gcr_gnupg_key_get_icon (GcrGnupgKey *self)
+{
+	g_return_val_if_fail (GCR_IS_GNUPG_KEY (self), NULL);
+
+	if (self->pv->icon == NULL) {
+		self->pv->icon = load_user_attribute_icon (self);
+		if (self->pv->icon == NULL)
+			self->pv->icon = g_themed_icon_new ("folder"); /* TODO: proper icon */
+	}
+
+	return self->pv->icon;
+}
+
 /**
  * _gcr_gnupg_key_get_columns:
  *
@@ -421,12 +507,14 @@ const GcrColumn*
 _gcr_gnupg_key_get_columns (void)
 {
 	static GcrColumn columns[] = {
+		{ "icon", /* later */ 0, /* later */ 0, NULL, 0, NULL, 0 },
 		{ "label", G_TYPE_STRING, G_TYPE_STRING, NC_("column", "Name"),
-		  GCR_COLUMN_SORTABLE },
+		  GCR_COLUMN_SORTABLE, NULL, 0 },
 		{ "short-keyid", G_TYPE_STRING, G_TYPE_STRING, NC_("column", "Key ID"),
-		  GCR_COLUMN_SORTABLE },
+		  GCR_COLUMN_SORTABLE, NULL, 0 },
 		{ NULL }
 	};
 
+	columns[0].property_type = columns[0].column_type = G_TYPE_ICON;
 	return columns;
 }
diff --git a/gcr/gcr-gnupg-key.h b/gcr/gcr-gnupg-key.h
index dcef960..745bec2 100644
--- a/gcr/gcr-gnupg-key.h
+++ b/gcr/gcr-gnupg-key.h
@@ -76,8 +76,12 @@ GPtrArray*          _gcr_gnupg_key_get_secret_records            (GcrGnupgKey *s
 void                _gcr_gnupg_key_set_secret_records            (GcrGnupgKey *self,
                                                                   GPtrArray *records);
 
+GIcon*              _gcr_gnupg_key_get_icon                      (GcrGnupgKey *self);
+
 const gchar*        _gcr_gnupg_key_get_keyid_for_records         (GPtrArray *records);
 
+const gchar*        _gcr_gnupg_key_get_fingerprint_for_records   (GPtrArray *records);
+
 G_END_DECLS
 
 #endif /* __GCR_GNUPG_KEY_H__ */
diff --git a/gcr/gcr-gnupg-util.c b/gcr/gcr-gnupg-util.c
new file mode 100644
index 0000000..5e0025f
--- /dev/null
+++ b/gcr/gcr-gnupg-util.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw@collabora.co.uk>
+ */
+
+#include "config.h"
+
+#include "egg/egg-hex.h"
+
+#include "gcr-gnupg-util.h"
+
+#include <gcrypt.h>
+
+GcrRecord*
+_gcr_gnupg_build_xa1_record (GcrRecord *meta, gpointer attribute,
+                             gsize n_attribute)
+{
+	gchar hash[20];
+	gchar *hex;
+	gchar *status = "";
+	gint state, save;
+	gsize length;
+	gsize n_prefix, estimate;
+	GString *output;
+	GcrRecord *record;
+	guint flags, type;
+	const gchar *fingerprint, *created, *expiry;
+
+	g_return_val_if_fail (meta, NULL);
+
+	gcry_md_hash_buffer (GCRY_MD_RMD160, hash, attribute, n_attribute);
+	hex = egg_hex_encode_full (hash, sizeof (hash), TRUE, 0, 1);
+
+	if (!_gcr_record_get_uint (meta, GCR_RECORD_ATTRIBUTE_FLAGS, &flags))
+		flags = 0;
+
+	if (!_gcr_record_get_uint (meta, GCR_RECORD_ATTRIBUTE_TYPE, &type))
+		type = 0;
+
+	fingerprint = _gcr_record_get_raw (meta, GCR_RECORD_ATTRIBUTE_FINGERPRINT);
+	if (fingerprint == NULL)
+		fingerprint = "";
+
+	created = _gcr_record_get_raw (meta, GCR_RECORD_ATTRIBUTE_TIMESTAMP);
+	if (created == NULL)
+		created = "0";
+
+	expiry = _gcr_record_get_raw (meta, GCR_RECORD_ATTRIBUTE_EXPIRY);
+	if (expiry == NULL)
+		expiry = "";
+
+	if (flags & 0x02)
+		status = "r";
+	else if (flags & 0x04)
+		status = "e";
+	else if (flags & 0x01)
+		status = "P";
+
+	/* Algorithm from Glib reference */
+	estimate = n_attribute * 4 / 3 + n_attribute * 4 / (3 * 65) + 7;
+
+	output = g_string_sized_new (64 + estimate);
+	g_string_append_printf (output, "xa1::%u:%u:%s:%s:%s:%s:%s:",
+	                        (guint)n_attribute, type, fingerprint,
+	                        created, expiry, hex, status);
+
+	g_free (hex);
+
+	/* Resize string to fit the base64 data. */
+	n_prefix = output->len;
+	g_string_set_size (output, n_prefix + estimate);
+
+	/* The actual base64 data, without line breaks */
+	state = save = 0;
+	length = g_base64_encode_step ((guchar*)attribute, n_attribute, FALSE,
+	                               output->str + n_prefix, &state, &save);
+	length += g_base64_encode_close (TRUE, output->str + n_prefix + length,
+	                                 &state, &save);
+
+	g_assert (length <= estimate);
+	g_string_set_size (output, n_prefix + length);
+	record = _gcr_record_take_colons (g_string_free (output, FALSE));
+	g_assert (record);
+
+	return record;
+}
diff --git a/gcr/gcr-gnupg-util.h b/gcr/gcr-gnupg-util.h
new file mode 100644
index 0000000..49b0e4f
--- /dev/null
+++ b/gcr/gcr-gnupg-util.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw@collabora.co.uk>
+ */
+
+#if !defined (__GCR_H_INSIDE__) && !defined (GCR_COMPILATION)
+#error "Only <gcr/gcr.h> can be included directly."
+#endif
+
+#ifndef GCR_GNUPG_UTIL_H
+#define GCR_GNUPG_UTIL_H
+
+#include <glib.h>
+
+#include "gcr-record.h"
+
+G_BEGIN_DECLS
+
+GcrRecord*          _gcr_gnupg_build_xa1_record              (GcrRecord *meta,
+                                                              gpointer attribute,
+                                                              gsize n_attribute);
+
+G_END_DECLS
+
+#endif /* __GCR_GNUPG_KEY_H__ */
diff --git a/gcr/gcr-memory-icon.c b/gcr/gcr-memory-icon.c
new file mode 100644
index 0000000..83fb5ea
--- /dev/null
+++ b/gcr/gcr-memory-icon.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw@collabora.co.uk>
+ */
+
+#include "config.h"
+
+#include "gcr-memory-icon.h"
+
+#include <string.h>
+
+struct _GcrMemoryIconPrivate {
+	gpointer data;
+	gsize n_data;
+	goffset offset;
+	gchar *image_type;
+	GDestroyNotify destroy;
+};
+
+/* Forward declarations */
+static void _gcr_memory_icon_iface_icon (GIconIface *iface);
+static void _gcr_memory_icon_iface_loadable_icon (GLoadableIconIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GcrMemoryIcon, _gcr_memory_icon, G_TYPE_OBJECT,
+	G_IMPLEMENT_INTERFACE (G_TYPE_ICON, _gcr_memory_icon_iface_icon);
+	G_IMPLEMENT_INTERFACE (G_TYPE_LOADABLE_ICON, _gcr_memory_icon_iface_loadable_icon);
+);
+
+
+static void
+_gcr_memory_icon_init (GcrMemoryIcon *self)
+{
+	self->pv = (G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_MEMORY_ICON, GcrMemoryIconPrivate));
+}
+
+static void
+_gcr_memory_icon_finalize (GObject *obj)
+{
+	GcrMemoryIcon *self = GCR_MEMORY_ICON (obj);
+
+	if (self->pv->destroy)
+		(self->pv->destroy) (self->pv->data);
+	g_free (self->pv->image_type);
+
+	G_OBJECT_CLASS (_gcr_memory_icon_parent_class)->finalize (obj);
+}
+
+static void
+_gcr_memory_icon_class_init (GcrMemoryIconClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	_gcr_memory_icon_parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GcrMemoryIconPrivate));
+
+	gobject_class->finalize = _gcr_memory_icon_finalize;
+}
+
+static gboolean
+_gcr_memory_icon_equal (GIcon *icon1, GIcon *icon2)
+{
+	GcrMemoryIcon *one = GCR_MEMORY_ICON (icon1);
+	GcrMemoryIcon *two = GCR_MEMORY_ICON (icon2);
+
+	if (icon1 == icon2)
+		return TRUE;
+	if (!g_str_equal (one->pv->image_type, two->pv->image_type))
+		return FALSE;
+	if ((one->pv->n_data - one->pv->offset) != (two->pv->n_data - two->pv->offset))
+		return FALSE;
+	return memcmp ((guchar*)one->pv->data + one->pv->offset,
+	               (guchar*)two->pv->data + two->pv->offset,
+	               one->pv->n_data - one->pv->offset) == 0;
+}
+
+static guint
+_gcr_memory_icon_hash (GIcon *icon)
+{
+	GcrMemoryIcon *self = GCR_MEMORY_ICON (icon);
+	const signed char *p, *end;
+	guint32 hash;
+
+	hash = g_str_hash (self->pv->image_type);
+
+	/* Adapted from g_str_hash */
+	p = self->pv->data;
+	end = p + self->pv->n_data;
+	p += self->pv->offset;
+	while (p < end)
+		hash = (hash << 5) + hash + *(p++);
+
+	return hash;
+}
+
+static void
+_gcr_memory_icon_iface_icon (GIconIface *iface)
+{
+	iface->equal = _gcr_memory_icon_equal;
+	iface->hash = _gcr_memory_icon_hash;
+}
+
+static GInputStream *
+_gcr_memory_icon_load (GLoadableIcon *icon, int size, gchar **type,
+                       GCancellable *cancellable, GError **error)
+{
+	GcrMemoryIcon *self = GCR_MEMORY_ICON (icon);
+	GInputStream *is;
+
+	if (type != NULL)
+		*type = g_strdup (self->pv->image_type);
+
+	is = g_memory_input_stream_new_from_data ((guchar*)self->pv->data + self->pv->offset,
+	                                          self->pv->n_data, NULL);
+	g_object_set_data_full (G_OBJECT (is), "back-reference", g_object_ref (self),
+	                        g_object_unref);
+
+	return is;
+}
+
+static void
+_gcr_memory_icon_load_async (GLoadableIcon *icon, int size, GCancellable *cancellable,
+                             GAsyncReadyCallback callback, gpointer user_data)
+{
+	GSimpleAsyncResult *res;
+
+	res = g_simple_async_result_new (G_OBJECT (icon), callback, user_data,
+	                                 _gcr_memory_icon_load_async);
+
+	g_simple_async_result_complete_in_idle (res);
+	g_object_unref (res);
+}
+
+static GInputStream*
+_gcr_memory_icon_finish (GLoadableIcon *icon, GAsyncResult *res, char **type,
+                         GError **error)
+{
+	g_return_val_if_fail (g_simple_async_result_is_valid (res, G_OBJECT (icon),
+	                      _gcr_memory_icon_load_async), NULL);
+	return _gcr_memory_icon_load (icon, 0, type, NULL, error);
+}
+
+
+static void
+_gcr_memory_icon_iface_loadable_icon (GLoadableIconIface *iface)
+{
+	iface->load = _gcr_memory_icon_load;
+	iface->load_async = _gcr_memory_icon_load_async;
+	iface->load_finish = _gcr_memory_icon_finish;
+}
+
+GIcon*
+_gcr_memory_icon_new (const gchar *image_type, gconstpointer data, gsize n_data)
+{
+	g_return_val_if_fail (image_type != NULL, NULL);
+	g_return_val_if_fail (data != NULL, NULL);
+
+	return _gcr_memory_icon_new_full (image_type, g_memdup (data, n_data),
+	                                  n_data, 0, g_free);
+}
+
+GIcon*
+_gcr_memory_icon_new_full (const gchar *image_type, gpointer data, gsize n_data,
+                           goffset offset, GDestroyNotify destroy)
+{
+	GcrMemoryIcon *self;
+
+	g_return_val_if_fail (image_type != NULL, NULL);
+	g_return_val_if_fail (data != NULL, NULL);
+	g_return_val_if_fail (offset <= n_data, NULL);
+
+	self = g_object_new (GCR_TYPE_MEMORY_ICON, NULL);
+	self->pv->data = data;
+	self->pv->n_data = n_data;
+	self->pv->offset = offset;
+	self->pv->destroy = destroy;
+	self->pv->image_type = g_strdup (image_type);
+
+	return G_ICON (self);
+}
diff --git a/gcr/gcr-memory-icon.h b/gcr/gcr-memory-icon.h
new file mode 100644
index 0000000..c02767f
--- /dev/null
+++ b/gcr/gcr-memory-icon.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw@collabora.co.uk>
+ */
+
+#ifndef GCR_MEMORY_ICON_H
+#define GCR_MEMORY_ICON_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GCR_MEMORY_ICON_COLUMNS            (_gcr_memory_icon_get_columns ())
+#define GCR_TYPE_MEMORY_ICON               (_gcr_memory_icon_get_type ())
+#define GCR_MEMORY_ICON(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_MEMORY_ICON, GcrMemoryIcon))
+#define GCR_MEMORY_ICON_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_MEMORY_ICON, GcrMemoryIconClass))
+#define GCR_IS_MEMORY_ICON(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_MEMORY_ICON))
+#define GCR_IS_MEMORY_ICON_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_MEMORY_ICON))
+#define GCR_MEMORY_ICON_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_MEMORY_ICON, GcrMemoryIconClass))
+
+typedef struct _GcrMemoryIcon GcrMemoryIcon;
+typedef struct _GcrMemoryIconClass GcrMemoryIconClass;
+typedef struct _GcrMemoryIconPrivate GcrMemoryIconPrivate;
+
+struct _GcrMemoryIcon {
+	GObject parent;
+
+	/*< private >*/
+	GcrMemoryIconPrivate *pv;
+};
+
+struct _GcrMemoryIconClass {
+	GObjectClass parent_class;
+};
+
+GType               _gcr_memory_icon_get_type             (void);
+
+GIcon*              _gcr_memory_icon_new                  (const gchar *image_type,
+                                                           gconstpointer data,
+                                                           gsize n_data);
+
+GIcon*              _gcr_memory_icon_new_full             (const gchar *image_type,
+                                                           gpointer data,
+                                                           gsize n_data,
+                                                           goffset offset,
+                                                           GDestroyNotify destroy);
+
+G_END_DECLS
+
+#endif /* __GCR_MEMORY_ICON_H__ */
diff --git a/gcr/gcr-record.c b/gcr/gcr-record.c
index cd33b2d..a417d0d 100644
--- a/gcr/gcr-record.c
+++ b/gcr/gcr-record.c
@@ -216,6 +216,20 @@ _gcr_record_get_uint (GcrRecord *record, guint column, guint *value)
 	return TRUE;
 }
 
+gpointer
+_gcr_record_get_base64 (GcrRecord *record, guint column, gsize *n_data)
+{
+	const gchar *raw;
+
+	g_return_val_if_fail (record, NULL);
+
+	raw = _gcr_record_get_raw (record, column);
+	if (raw == NULL)
+		return NULL;
+
+	return g_base64_decode (raw, n_data);
+}
+
 const gchar*
 _gcr_record_get_raw (GcrRecord *record, guint column)
 {
diff --git a/gcr/gcr-record.h b/gcr/gcr-record.h
index 3cc6d8d..937ef75 100644
--- a/gcr/gcr-record.h
+++ b/gcr/gcr-record.h
@@ -51,9 +51,12 @@
 
 G_BEGIN_DECLS
 
-#define GCR_RECORD_SCHEMA_UID  (g_quark_from_static_string ("uid"))
+#define GCR_RECORD_SCHEMA_ATTRIBUTE  (g_quark_from_static_string ("ATTRIBUTE"))
+#define GCR_RECORD_SCHEMA_FPR  (g_quark_from_static_string ("fpr"))
 #define GCR_RECORD_SCHEMA_PUB  (g_quark_from_static_string ("pub"))
 #define GCR_RECORD_SCHEMA_SEC  (g_quark_from_static_string ("sec"))
+#define GCR_RECORD_SCHEMA_UID  (g_quark_from_static_string ("uid"))
+#define GCR_RECORD_SCHEMA_XA1  (g_quark_from_static_string ("xa1"))
 
 /* Common columns for all schemas */
 typedef enum {
@@ -61,6 +64,28 @@ typedef enum {
 } GcrRecordColumns;
 
 /*
+ * Columns for ATTRIBUTE status message. eg:
+ * [GNUPG:] ATTRIBUTE FBAFC70D60AE13D560764062B547B5580EEB5A80 10604 1 1 1 1227936754 0 1
+ */
+typedef enum {
+	GCR_RECORD_ATTRIBUTE_FINGERPRINT = 1,
+	GCR_RECORD_ATTRIBUTE_LENGTH = 2,
+	GCR_RECORD_ATTRIBUTE_TYPE = 3,
+	GCR_RECORD_ATTRIBUTE_TIMESTAMP = 6,
+	GCR_RECORD_ATTRIBUTE_EXPIRY = 7,
+	GCR_RECORD_ATTRIBUTE_FLAGS = 8
+} GcrRecordAttributeColumns;
+
+/*
+ * Columns for fpr schema, add them as they're used. eg:
+ * fpr:::::::::ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:
+ */
+typedef enum {
+	GCR_RECORD_FPR_FINGERPRINT = 9
+} GcrRecordFprColumns;
+
+
+/*
  * Columns for pub schema, add them as they're used. eg:
  * pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
  */
@@ -84,6 +109,22 @@ typedef enum {
 	GCR_RECORD_UID_NAME = 9
 } GcrRecordUidColumns;
 
+/*
+ * Columns for xa1 schema. This is a schema that we've invented ourselves
+ * for representing the actual data of openpgp attribute packets. eg:
+ * xa1::10838:1:ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:1998-02-02:0:ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:P:...
+ */
+typedef enum {
+	GCR_RECORD_XA1_LENGTH = 2,
+	GCR_RECORD_XA1_TYPE = 3,
+	GCR_RECORD_XA1_FINGERPRINT = 4,
+	GCR_RECORD_XA1_TIMESTAMP = 5,
+	GCR_RECORD_XA1_EXPIRY = 6,
+	GCR_RECORD_XA1_HASH = 7,
+	GCR_RECORD_XA1_STATUS = 8,
+	GCR_RECORD_XA1_DATA = 9,
+} GcrRecordXa1Columns;
+
 typedef struct _GcrRecord GcrRecord;
 
 #define        GCR_TYPE_RECORD                  (_gcr_record_get_type ())
@@ -114,6 +155,10 @@ gboolean       _gcr_record_get_uint             (GcrRecord *record,
                                                  guint column,
                                                  guint *value);
 
+gpointer       _gcr_record_get_base64           (GcrRecord *record,
+                                                 guint column,
+                                                 gsize *n_data);
+
 const gchar*   _gcr_record_get_raw              (GcrRecord *record,
                                                  guint column);
 
diff --git a/gcr/tests/Makefile.am b/gcr/tests/Makefile.am
index b497a5f..0edb4cf 100644
--- a/gcr/tests/Makefile.am
+++ b/gcr/tests/Makefile.am
@@ -28,6 +28,7 @@ TEST_PROGS = \
 	test-trust \
 	test-parser \
 	test-record \
+	test-memory-icon \
 	test-gnupg-key \
 	test-gnupg-collection \
 	test-gnupg-process
diff --git a/gcr/tests/frob-gnupg-selector.c b/gcr/tests/frob-gnupg-selector.c
index dfea94e..f857db7 100644
--- a/gcr/tests/frob-gnupg-selector.c
+++ b/gcr/tests/frob-gnupg-selector.c
@@ -58,7 +58,7 @@ main (int argc, char *argv[])
 	g_object_ref_sink (dialog);
 
 	collection = _gcr_gnupg_collection_new (NULL);
-	selector = gcr_selector_new (collection, GCR_GNUPG_KEY_COLUMNS, GCR_SELECTOR_MODE_MULTIPLE);
+	selector = gcr_selector_new (collection, GCR_GNUPG_KEY_COLUMNS, GCR_SELECTOR_MODE_SINGLE);
 
 	gtk_widget_show (GTK_WIDGET (selector));
 	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (dialog)), GTK_WIDGET (selector));
diff --git a/gcr/tests/test-gnupg-collection.c b/gcr/tests/test-gnupg-collection.c
index c164ea6..3808596 100644
--- a/gcr/tests/test-gnupg-collection.c
+++ b/gcr/tests/test-gnupg-collection.c
@@ -25,6 +25,7 @@
 #include "gcr/gcr.h"
 #include "gcr/gcr-gnupg-collection.h"
 #include "gcr/gcr-gnupg-key.h"
+#include "gcr/gcr-record.h"
 
 #include "egg/egg-testing.h"
 
@@ -35,6 +36,7 @@
 
 typedef struct {
 	GcrGnupgCollection *collection;
+	gchar *directory;
 	GHashTable *keys;
 	GAsyncResult *result;
 } Test;
@@ -81,19 +83,17 @@ setup (Test *test, gconstpointer unused)
 {
 	GcrCollection *collection;
 	gchar *directory;
-	gchar *path;
 
 	directory = g_get_current_dir ();
-	path = g_build_filename (directory, "files", "gnupg-homedir", NULL);
+	test->directory = g_build_filename (directory, "files", "gnupg-homedir", NULL);
 
-	collection = _gcr_gnupg_collection_new (path);
+	collection = _gcr_gnupg_collection_new (test->directory);
 	test->collection = GCR_GNUPG_COLLECTION (collection);
 
 	test->keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 	g_signal_connect (collection, "added", G_CALLBACK (on_collection_added), test);
 	g_signal_connect (collection, "removed", G_CALLBACK (on_collection_removed), test);
 
-	g_free (path);
 	g_free (directory);
 }
 
@@ -106,6 +106,7 @@ teardown (Test *test, gconstpointer unused)
 		g_object_unref (test->result);
 
 	g_object_unref (test->collection);
+	g_free (test->directory);
 }
 
 static void
@@ -119,13 +120,24 @@ on_async_ready (GObject *source, GAsyncResult *res, gpointer user_data)
 }
 
 static void
+test_properties (Test *test, gconstpointer unused)
+{
+	gchar *directory;
+	g_object_get (test->collection, "directory", &directory, NULL);
+	g_assert_cmpstr (directory, ==, test->directory);
+	g_free (directory);
+}
+
+static void
 test_load (Test *test, gconstpointer unused)
 {
 	GError *error = NULL;
 	GcrGnupgKey *key;
+	GList *l, *objects;
+	GcrRecord *record;
 
 	_gcr_gnupg_collection_load_async (test->collection, NULL, on_async_ready, test);
-	egg_test_wait_until (500);
+	egg_test_wait_until (500000);
 
 	g_assert (test->result);
 	_gcr_gnupg_collection_load_finish (test->collection, test->result, &error);
@@ -140,6 +152,26 @@ test_load (Test *test, gconstpointer unused)
 	key = g_hash_table_lookup (test->keys, "268FEE686262C395");
 	g_assert (GCR_IS_GNUPG_KEY (key));
 	g_assert (_gcr_gnupg_key_get_secret_records (key));
+
+	/* The length of collection should be correct */
+	g_assert_cmpuint (g_hash_table_size (test->keys), ==,
+	                  gcr_collection_get_length (GCR_COLLECTION (test->collection)));
+
+	/* The list of objects should be correct */
+	objects = gcr_collection_get_objects (GCR_COLLECTION (test->collection));
+	g_assert_cmpuint (g_hash_table_size (test->keys), ==, g_list_length (objects));
+	for (l = objects; l != NULL; l = g_list_next (l)) {
+		g_assert (GCR_IS_GNUPG_KEY (l->data));
+		key = g_hash_table_lookup (test->keys, _gcr_gnupg_key_get_keyid (l->data));
+		g_assert (key == l->data);
+	}
+	g_list_free (objects);
+
+	/* Phillip R. Zimmerman's key should have a photo */
+	key = g_hash_table_lookup (test->keys, "C7463639B2D7795E");
+	g_assert (GCR_IS_GNUPG_KEY (key));
+	record = _gcr_record_find (_gcr_gnupg_key_get_public_records (key), GCR_RECORD_SCHEMA_XA1);
+	g_assert (record);
 }
 
 static void
@@ -158,7 +190,7 @@ test_reload (Test *test, gconstpointer unused)
 	test->result = NULL;
 
 	_gcr_gnupg_collection_load_async (test->collection, NULL, on_async_ready, test);
-	egg_test_wait_until (500);
+	egg_test_wait_until (500000);
 	g_assert (test->result);
 	_gcr_gnupg_collection_load_finish (test->collection, test->result, &error);
 	g_assert_no_error (error);
@@ -187,6 +219,7 @@ main (int argc, char **argv)
 	if (srcdir && chdir (srcdir) < 0)
 		g_error ("couldn't change directory to: %s: %s", srcdir, g_strerror (errno));
 
+	g_test_add ("/gcr/gnupg-collection/properties", Test, NULL, setup, test_properties, teardown);
 	g_test_add ("/gcr/gnupg-collection/load", Test, NULL, setup, test_load, teardown);
 	g_test_add ("/gcr/gnupg-collection/reload", Test, NULL, setup, test_reload, teardown);
 
diff --git a/gcr/tests/test-memory-icon.c b/gcr/tests/test-memory-icon.c
new file mode 100644
index 0000000..ebf200b
--- /dev/null
+++ b/gcr/tests/test-memory-icon.c
@@ -0,0 +1,206 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2011 Collabora Ltd
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Stef Walter <stefw@collabora.co.uk>
+*/
+
+#include "config.h"
+
+#include "gcr/gcr-memory-icon.h"
+
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+	GIcon *icon;
+	GAsyncResult *result;
+} Test;
+
+static const unsigned char test_data[256] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+	16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+	32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+	48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+	64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+	80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+	96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+	112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
+	128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+	144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+	160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+	176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
+	192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
+	208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
+	224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+	240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
+};
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+	test->icon = _gcr_memory_icon_new ("application/octet-stream",
+	                                   test_data, sizeof (test_data));
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+	g_object_unref (test->icon);
+	if (test->result)
+		g_object_unref (test->result);
+}
+
+static void
+test_equal_same (Test *test, gconstpointer unused)
+{
+	g_assert (g_icon_equal (test->icon, test->icon) == TRUE);
+}
+
+static void
+test_equal_not (Test *test, gconstpointer unused)
+{
+	GIcon *icon;
+
+	icon = g_themed_icon_new ("folder");
+	g_assert (g_icon_equal (test->icon, icon) == FALSE);
+	g_object_unref (icon);
+}
+
+static void
+test_equal_data (Test *test, gconstpointer unused)
+{
+	GIcon *icon;
+
+	icon = _gcr_memory_icon_new ("application/octet-stream",
+	                             test_data, sizeof (test_data));
+	g_assert (g_icon_equal (test->icon, icon) == TRUE);
+	g_assert_cmpuint (g_icon_hash (test->icon), ==, g_icon_hash (icon));
+	g_object_unref (icon);
+}
+
+static void
+test_different_type (Test *test, gconstpointer unused)
+{
+	GIcon *icon;
+
+	icon = _gcr_memory_icon_new ("application/x-other",
+	                             test_data, sizeof (test_data));
+	g_assert (g_icon_equal (test->icon, icon) == FALSE);
+	g_assert_cmpuint (g_icon_hash (test->icon), !=, g_icon_hash (icon));
+	g_object_unref (icon);
+}
+
+static void
+test_different_offset (Test *test, gconstpointer unused)
+{
+	GIcon *icon;
+
+	icon = _gcr_memory_icon_new_full ("application/octet-stream", (gpointer)test_data,
+	                                  sizeof (test_data) - 16, 16, NULL);
+	g_assert (g_icon_equal (test->icon, icon) == FALSE);
+	g_assert_cmpuint (g_icon_hash (test->icon), !=, g_icon_hash (icon));
+	g_object_unref (icon);
+}
+
+static void
+test_load_sync (Test *test, gconstpointer unused)
+{
+	GError *error = NULL;
+	GInputStream *is;
+	gchar buf[1024];
+	gsize length;
+	gchar *type;
+
+	is = g_loadable_icon_load (G_LOADABLE_ICON (test->icon), 0, &type, NULL, &error);
+	g_assert (is != NULL);
+	g_assert_no_error (error);
+	g_assert_cmpstr (type, ==, "application/octet-stream");
+
+	if (!g_input_stream_read_all (is, buf, sizeof (buf), &length, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+	egg_assert_cmpsize (length, ==, sizeof (test_data));
+	egg_assert_cmpmem (buf, length, ==, test_data, sizeof (test_data));
+
+	g_free (type);
+	g_object_unref (is);
+}
+
+static void
+on_async_ready (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+	Test *test = user_data;
+
+	g_assert (G_OBJECT (test->icon) == source);
+	g_assert (test->result == NULL);
+	g_assert (g_async_result_get_source_object (result) == source);
+
+	test->result = g_object_ref (result);
+	egg_test_wait_stop ();
+}
+
+static void
+test_load_async (Test *test, gconstpointer unused)
+{
+	GError *error = NULL;
+	GInputStream *is;
+	gchar buf[1024];
+	gsize length;
+	gchar *type;
+
+	g_loadable_icon_load_async (G_LOADABLE_ICON (test->icon), 0, NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	is = g_loadable_icon_load_finish (G_LOADABLE_ICON (test->icon), test->result, &type, &error);
+	g_assert (is != NULL);
+	g_assert_no_error (error);
+	g_assert_cmpstr (type, ==, "application/octet-stream");
+
+	if (!g_input_stream_read_all (is, buf, sizeof (buf), &length, NULL, &error))
+		g_assert_not_reached ();
+	g_assert_no_error (error);
+	egg_assert_cmpsize (length, ==, sizeof (test_data));
+	egg_assert_cmpmem (buf, length, ==, test_data, sizeof (test_data));
+
+	g_free (type);
+	g_object_unref (is);
+}
+
+int
+main (int argc, char **argv)
+{
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+
+	g_test_add ("/gcr/memory-icon/equal_same", Test, NULL, setup, test_equal_same, teardown);
+	g_test_add ("/gcr/memory-icon/equal_not", Test, NULL, setup, test_equal_not, teardown);
+	g_test_add ("/gcr/memory-icon/equal_data", Test, NULL, setup, test_equal_data, teardown);
+	g_test_add ("/gcr/memory-icon/different_type", Test, NULL, setup, test_different_type, teardown);
+	g_test_add ("/gcr/memory-icon/different_offset", Test, NULL, setup, test_different_offset, teardown);
+	g_test_add ("/gcr/memory-icon/load_sync", Test, NULL, setup, test_load_sync, teardown);
+	g_test_add ("/gcr/memory-icon/load_async", Test, NULL, setup, test_load_async, teardown);
+
+	return egg_tests_run_in_thread_with_loop ();
+}
diff --git a/gcr/tests/test-record.c b/gcr/tests/test-record.c
index 265d677..4c132ff 100644
--- a/gcr/tests/test-record.c
+++ b/gcr/tests/test-record.c
@@ -35,7 +35,7 @@ typedef struct {
 static void
 setup (Test *test, gconstpointer unused)
 {
-	test->record = _gcr_record_parse_colons ("one:tab\\there::four:f\xfc""nf:3533333:-88", -1);
+	test->record = _gcr_record_parse_colons ("one:tab\\there::YW9ldTM4Mzg=:f\xfc""nf:3533333:-88", -1);
 }
 
 static void
@@ -215,6 +215,21 @@ test_get_uint_invalid (Test *test, gconstpointer unused)
 }
 
 static void
+test_get_base64 (Test *test, gconstpointer unused)
+{
+	gchar *value;
+	gsize n_value;
+
+	value = _gcr_record_get_base64 (test->record, 3, &n_value);
+	g_assert (value);
+	egg_assert_cmpsize (n_value, ==, 8);
+	g_assert (memcmp (value, "aoeu3838", n_value) == 0);
+
+	g_free (value);
+}
+
+
+static void
 test_free_null (void)
 {
 	_gcr_record_free (NULL);
@@ -294,6 +309,7 @@ main (int argc, char **argv)
 	g_test_add ("/gcr/record/get_uint", Test, NULL, setup, test_get_uint, teardown);
 	g_test_add ("/gcr/record/get_uint_invalid", Test, NULL, setup, test_get_uint_invalid, teardown);
 	g_test_add ("/gcr/record/get_uint_range", Test, NULL, setup, test_get_uint_range, teardown);
+	g_test_add ("/gcr/record/get_base64", Test, NULL, setup, test_get_base64, teardown);
 	g_test_add ("/gcr/record/get_schema", Test, NULL, setup, test_get_schema, teardown);
 
 	return g_test_run ();
