[seahorse] Reimplement export functionality



commit 27614a2257ac0c663eb1372631c1870852eff05f
Author: Stef Walter <stefw collabora co uk>
Date:   Mon Nov 28 12:23:16 2011 +0100

    Reimplement export functionality
    
     * SeahorseExportable interface on an object indicates export capability.
     * SeahorseExporter represents a format for a given set of objects.
     * Multiple objects can be combined using seahorse_exportable_add_object()
       if the format supports it.
     * So far only implemented one certificate exporter.

 libseahorse/Makefile.am                    |    2 +
 libseahorse/seahorse-exportable.c          |  427 ++++++++++++++++++++++++++
 libseahorse/seahorse-exportable.h          |   72 +++++
 libseahorse/seahorse-exporter.c            |  273 +++++++++++++++++
 libseahorse/seahorse-exporter.h            |  101 +++++++
 libseahorse/seahorse-key-manager-store.c   |   88 ++----
 libseahorse/seahorse-place.c               |  168 -----------
 libseahorse/seahorse-place.h               |   37 ---
 libseahorse/seahorse-util.c                |  285 +++---------------
 libseahorse/seahorse-util.h                |   19 +-
 libseahorse/seahorse-viewer.c              |  165 +++--------
 pgp/Makefile.am                            |    1 +
 pgp/seahorse-gpg-op.c                      |   53 ++--
 pgp/seahorse-gpg-op.h                      |    2 +-
 pgp/seahorse-gpgme-exporter.c              |  445 ++++++++++++++++++++++++++++
 pgp/seahorse-gpgme-exporter.h              |   47 +++
 pgp/seahorse-gpgme-key.c                   |   48 +++-
 pgp/seahorse-gpgme-keyring.c               |  152 +----------
 pgp/seahorse-hkp-source.c                  |   48 ++--
 pgp/seahorse-ldap-source.c                 |   56 ++--
 pgp/seahorse-pgp-key-properties.c          |   82 ++----
 pgp/seahorse-server-source.c               |   10 +-
 pgp/seahorse-server-source.h               |    8 +-
 pgp/seahorse-transfer.c                    |   67 +++--
 pkcs11/Makefile.am                         |    1 +
 pkcs11/seahorse-certificate-der-exporter.c |  245 +++++++++++++++
 pkcs11/seahorse-certificate-der-exporter.h |   41 +++
 pkcs11/seahorse-certificate.c              |   27 ++-
 pkcs11/seahorse-pkcs11-properties.c        |   22 ++-
 po/POTFILES.in                             |    4 +
 src/seahorse-key-manager.ui                |    2 +
 ssh/Makefile.am                            |    1 +
 ssh/seahorse-ssh-exporter.c                |  321 ++++++++++++++++++++
 ssh/seahorse-ssh-exporter.h                |   42 +++
 ssh/seahorse-ssh-key-properties.c          |   77 ++---
 ssh/seahorse-ssh-key.c                     |   37 ++-
 ssh/seahorse-ssh-key.h                     |    2 +
 ssh/seahorse-ssh-operation.c               |  114 +++-----
 ssh/seahorse-ssh-source.c                  |   82 -----
 39 files changed, 2464 insertions(+), 1210 deletions(-)
---
diff --git a/libseahorse/Makefile.am b/libseahorse/Makefile.am
index 83f1c4d..d1a4b15 100644
--- a/libseahorse/Makefile.am
+++ b/libseahorse/Makefile.am
@@ -37,6 +37,8 @@ libseahorse_la_SOURCES = \
 	seahorse-context.c seahorse-context.h \
 	seahorse-debug.c seahorse-debug.h \
 	seahorse-delete-dialog.c seahorse-delete-dialog.h \
+	seahorse-exportable.c seahorse-exportable.h \
+	seahorse-exporter.c seahorse-exporter.h \
 	seahorse-icons.c seahorse-icons.h \
 	seahorse-key-manager-store.c seahorse-key-manager-store.h \
 	seahorse-object.c seahorse-object.h \
diff --git a/libseahorse/seahorse-exportable.c b/libseahorse/seahorse-exportable.c
new file mode 100644
index 0000000..ad4bad5
--- /dev/null
+++ b/libseahorse/seahorse-exportable.c
@@ -0,0 +1,427 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004,2005 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU 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.
+ */
+
+#include "config.h"
+
+#include "seahorse-exportable.h"
+#include "seahorse-exporter.h"
+
+#include <glib/gi18n.h>
+
+#include <string.h>
+
+typedef SeahorseExportableIface SeahorseExportableInterface;
+
+G_DEFINE_INTERFACE (SeahorseExportable, seahorse_exportable, G_TYPE_OBJECT);
+
+GList *
+seahorse_exportable_create_exporters (SeahorseExportable *exportable,
+                                      SeahorseExporterType type)
+{
+	SeahorseExportableIface *iface;
+
+	g_return_val_if_fail (SEAHORSE_IS_EXPORTABLE (exportable), NULL);
+
+	iface = SEAHORSE_EXPORTABLE_GET_INTERFACE (exportable);
+	g_return_val_if_fail (iface->create_exporters, NULL);
+
+	return iface->create_exporters (exportable, type);
+}
+
+typedef struct {
+	GMainLoop *loop;
+	GAsyncResult *result;
+} WaitClosure;
+
+static WaitClosure *
+wait_closure_new (void)
+{
+	WaitClosure *closure;
+
+	closure = g_slice_new0 (WaitClosure);
+	closure->loop = g_main_loop_new (NULL, FALSE);
+
+	return closure;
+}
+
+static void
+wait_closure_free (WaitClosure *closure)
+{
+	g_clear_object (&closure->result);
+	g_main_loop_unref (closure->loop);
+	g_slice_free (WaitClosure, closure);
+}
+
+static void
+on_wait_complete (GObject *source,
+                  GAsyncResult *result,
+                  gpointer user_data)
+{
+	WaitClosure *closure = user_data;
+	g_assert (closure->result == NULL);
+	closure->result = g_object_ref (result);
+	g_main_loop_quit (closure->loop);
+}
+
+static void
+seahorse_exportable_default_init (SeahorseExportableIface *iface)
+{
+	static gboolean initialized = FALSE;
+	if (!initialized) {
+		initialized = TRUE;
+	}
+}
+
+guint
+seahorse_exportable_export_to_directory_wait (GList *objects,
+                                              const gchar *directory,
+                                              GError **error)
+{
+	SeahorseExporter *exporter;
+	WaitClosure *closure;
+	GList *exporters;
+	guint count = 0;
+	gboolean ret;
+	gchar *filename;
+	gchar *name;
+	GFile *file;
+	GList *l;
+
+	g_return_val_if_fail (directory != NULL, 0);
+	g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+	closure = wait_closure_new ();
+
+	for (l = objects; l != NULL; l = g_list_next (l)) {
+		if (!SEAHORSE_IS_EXPORTABLE (l->data))
+			continue;
+
+		exporters = seahorse_exportable_create_exporters (SEAHORSE_EXPORTABLE (l->data),
+		                                                  SEAHORSE_EXPORTER_ANY);
+		if (!exporters)
+			continue;
+
+		exporter = g_object_ref (exporters->data);
+		g_list_free_full (exporters, g_object_unref);
+
+		name = seahorse_exporter_get_filename (exporter);
+		filename = g_build_filename (directory, name, NULL);
+		g_free (name);
+
+		file = g_file_new_for_uri (filename);
+		seahorse_exporter_export_to_file_async (exporter, file, FALSE,
+		                                       NULL, on_wait_complete, closure);
+		g_object_unref (file);
+
+		g_main_loop_run (closure->loop);
+
+		ret = seahorse_exporter_export_to_file_finish (exporter, closure->result, error);
+		g_object_unref (closure->result);
+		closure->result = NULL;
+
+		g_object_unref (exporter);
+		g_free (filename);
+
+		if (ret)
+			count++;
+		else
+			break;
+	}
+
+	wait_closure_free (closure);
+	return count;
+}
+
+guint
+seahorse_exportable_export_to_text_wait (GList *objects,
+                                         gpointer *data,
+                                         gsize *size,
+                                         GError **error)
+{
+	SeahorseExporter *exporter = NULL;
+	WaitClosure *closure;
+	GList *exporters = NULL;
+	guint count, total;
+	GList *l, *e;
+
+	g_return_val_if_fail (error == NULL || *error == NULL, 0);
+	g_return_val_if_fail (data != NULL, 0);
+	g_return_val_if_fail (size != NULL, 0);
+
+	for (l = objects; l != NULL; l = g_list_next (l)) {
+		if (!SEAHORSE_IS_EXPORTABLE (l->data))
+			continue;
+
+		/* If we've already found exporters, then add to those */
+		if (exporters) {
+			for (e = exporters; e != NULL; e = g_list_next (e))
+				seahorse_exporter_add_object (e->data, l->data);
+
+		/* Otherwise try and create new exporters for this object */
+		} else {
+			exporters = seahorse_exportable_create_exporters (SEAHORSE_EXPORTABLE (l->data),
+			                                                  SEAHORSE_EXPORTER_TEXTUAL);
+		}
+	}
+
+	/* Find the exporter than has the most objects */
+	for (e = exporters, total = 0, exporter = NULL;
+	     e != NULL; e = g_list_next (e)) {
+		count = g_list_length (seahorse_exporter_get_objects (e->data));
+		if (count > total) {
+			total = count;
+			g_clear_object (&exporter);
+			exporter = g_object_ref (e->data);
+		}
+	}
+
+	g_list_free_full (exporters, g_object_unref);
+
+	if (exporter) {
+		closure = wait_closure_new ();
+
+		seahorse_exporter_export_async (exporter, NULL, on_wait_complete, closure);
+
+		g_main_loop_run (closure->loop);
+
+		*data = seahorse_exporter_export_finish (exporter, closure->result, size, error);
+		if (*data == NULL)
+			total = 0;
+
+		wait_closure_free (closure);
+		g_object_unref (exporter);
+	}
+
+	return total;
+}
+
+guint
+seahorse_exportable_export_to_prompt_wait (GList *objects,
+                                           GtkWindow *parent,
+                                           GError **error)
+{
+	SeahorseExporter *exporter;
+	gchar *directory = NULL;
+	WaitClosure *closure;
+	GHashTable *pending;
+	GList *exporters;
+	guint count = 0;
+	gboolean ret;
+	GList *l, *e, *x;
+	GList *exported;
+	GFile *file;
+
+	g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), 0);
+	g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+	/* A table for monitoring which objects are still pending */
+	pending = g_hash_table_new (g_direct_hash, g_direct_equal);
+	for (l = objects; l != NULL; l = g_list_next (l))
+		g_hash_table_replace (pending, l->data, l->data);
+
+	closure = wait_closure_new ();
+
+	for (l = objects; l != NULL; l = g_list_next (l)) {
+		if (!g_hash_table_lookup (pending, l->data))
+			continue;
+
+		if (!SEAHORSE_IS_EXPORTABLE (l->data)) {
+			g_hash_table_remove (pending, l->data);
+			continue;
+		}
+
+		exporters = seahorse_exportable_create_exporters (SEAHORSE_EXPORTABLE (l->data),
+		                                                  SEAHORSE_EXPORTER_ANY);
+		if (!exporters)
+			continue;
+
+		/* Now go and add all pending to each exporter */
+		for (x = objects; x != NULL; x = g_list_next (x)) {
+			if (x->data == l->data)
+				continue;
+			if (g_hash_table_lookup (pending, x->data)) {
+				for (e = exporters; e != NULL; e = g_list_next (e))
+					seahorse_exporter_add_object (e->data, x->data);
+			}
+		}
+
+		/* Now show a prompt choosing between the exporters */
+		ret = seahorse_exportable_prompt (exporters, parent,
+		                                  &directory, &file, &exporter);
+
+		g_list_free_full (exporters, g_object_unref);
+
+		if (!ret)
+			break;
+
+		seahorse_exporter_export_to_file_async (exporter, file, TRUE,
+		                                        NULL, on_wait_complete, closure);
+
+		g_main_loop_run (closure->loop);
+
+		ret = seahorse_exporter_export_to_file_finish (exporter, closure->result, error);
+		g_object_unref (closure->result);
+		closure->result = NULL;
+
+		if (ret) {
+			exported = seahorse_exporter_get_objects (exporter);
+			for (e = exported; e != NULL; e = g_list_next (e)) {
+				g_hash_table_remove (pending, e->data);
+				count++;
+			}
+		}
+
+		g_object_unref (file);
+		g_object_unref (exporter);
+
+		if (!ret)
+			break;
+	}
+
+	g_free (directory);
+	wait_closure_free (closure);
+	g_hash_table_destroy (pending);
+	return count;
+
+}
+
+static gchar *
+calculate_basename (GFile *file,
+                    const gchar *extension)
+{
+	gchar *basename = g_file_get_basename (file);
+	gchar *name;
+	gchar *cut;
+
+	cut = strrchr (basename, '.');
+	if (cut != NULL)
+		cut[0] = '\0';
+
+	name = g_strdup_printf ("%s%s", basename, extension);
+	g_free (basename);
+
+	return name;
+}
+
+static void
+on_filter_changed (GObject *obj,
+                   GParamSpec *pspec,
+                   gpointer user_data)
+{
+	GHashTable *filters = user_data;
+	GtkFileChooser *chooser = GTK_FILE_CHOOSER (obj);
+	SeahorseExporter *exporter;
+	GtkFileFilter *filter;
+	const gchar *extension;
+	gchar *basename;
+	gchar *name;
+	GFile *file;
+
+	filter = gtk_file_chooser_get_filter (chooser);
+	g_return_if_fail (filter != NULL);
+
+	exporter = g_hash_table_lookup (filters, filter);
+	g_return_if_fail (exporter != NULL);
+
+	name = seahorse_exporter_get_filename (exporter);
+	g_return_if_fail (name != NULL);
+
+	extension = strrchr (name, '.');
+	if (extension) {
+		file = gtk_file_chooser_get_file (chooser);
+		if (file) {
+			basename = calculate_basename (file, extension);
+			g_object_unref (file);
+			if (basename)
+				gtk_file_chooser_set_current_name (chooser, basename);
+			g_free (basename);
+		} else {
+			gtk_file_chooser_set_current_name (chooser, name);
+		}
+	}
+
+	g_free (name);
+}
+
+gboolean
+seahorse_exportable_prompt (GList *exporters,
+                            GtkWindow *parent,
+                            gchar **directory,
+                            GFile **file,
+                            SeahorseExporter **exporter)
+{
+	GtkWidget *widget;
+	GtkDialog *dialog;
+	GtkFileChooser *chooser;
+	GHashTable *filters;
+	GtkFileFilter *first = NULL;
+	GtkFileFilter *filter;
+	gboolean result = FALSE;
+	GList *e;
+
+	g_return_val_if_fail (exporters != NULL, FALSE);
+	g_return_val_if_fail (parent == NULL || GTK_WINDOW (parent), FALSE);
+	g_return_val_if_fail (file != NULL, FALSE);
+	g_return_val_if_fail (exporter != NULL, FALSE);
+
+	widget = gtk_file_chooser_dialog_new (NULL, parent, GTK_FILE_CHOOSER_ACTION_SAVE,
+	                                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+	                                      _("Export"), GTK_RESPONSE_ACCEPT,
+	                                      NULL);
+
+	dialog = GTK_DIALOG (widget);
+	chooser = GTK_FILE_CHOOSER (widget);
+
+	gtk_dialog_set_default_response (dialog, GTK_RESPONSE_ACCEPT);
+	gtk_file_chooser_set_local_only (chooser, FALSE);
+	gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
+
+	if (directory && *directory != NULL)
+		gtk_file_chooser_set_current_folder (chooser, *directory);
+
+	filters = g_hash_table_new (g_direct_hash, g_direct_equal);
+	for (e = exporters; e != NULL; e = g_list_next (e)) {
+		filter = seahorse_exporter_get_file_filter (e->data);
+		g_hash_table_replace (filters, filter, e->data);
+		gtk_file_chooser_add_filter (chooser, filter);
+		if (first == NULL)
+			first = filter;
+	}
+
+	g_signal_connect (chooser, "notify::filter", G_CALLBACK (on_filter_changed), filters);
+	gtk_file_chooser_set_filter (chooser, first);
+
+	if (gtk_dialog_run (dialog) == GTK_RESPONSE_ACCEPT) {
+		*file = gtk_file_chooser_get_file (chooser);
+		filter = gtk_file_chooser_get_filter (chooser);
+		*exporter = g_object_ref (g_hash_table_lookup (filters, filter));
+		if (directory) {
+			g_free (*directory);
+			*directory = g_strdup (gtk_file_chooser_get_current_folder (chooser));
+		}
+		result = TRUE;
+	}
+
+	g_hash_table_destroy (filters);
+	gtk_widget_destroy (widget);
+	return result;
+}
diff --git a/libseahorse/seahorse-exportable.h b/libseahorse/seahorse-exportable.h
new file mode 100644
index 0000000..4474151
--- /dev/null
+++ b/libseahorse/seahorse-exportable.h
@@ -0,0 +1,72 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Exportable, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#ifndef __SEAHORSE_EXPORTABLE_H__
+#define __SEAHORSE_EXPORTABLE_H__
+
+#include "seahorse-exporter.h"
+
+#include <gio/gio.h>
+
+#include <gtk/gtk.h>
+
+#define SEAHORSE_TYPE_EXPORTABLE                (seahorse_exportable_get_type ())
+#define SEAHORSE_EXPORTABLE(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAHORSE_TYPE_EXPORTABLE, SeahorseExportable))
+#define SEAHORSE_IS_EXPORTABLE(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAHORSE_TYPE_EXPORTABLE))
+#define SEAHORSE_EXPORTABLE_GET_INTERFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), SEAHORSE_TYPE_EXPORTABLE, SeahorseExportableIface))
+
+typedef struct _SeahorseExportable SeahorseExportable;
+typedef struct _SeahorseExportableIface SeahorseExportableIface;
+
+struct _SeahorseExportableIface {
+	GTypeInterface parent;
+
+	GList *     (* create_exporters)      (SeahorseExportable *exportable,
+	                                       SeahorseExporterType type);
+};
+
+GType       seahorse_exportable_get_type                    (void) G_GNUC_CONST;
+
+GList *     seahorse_exportable_create_exporters            (SeahorseExportable *exportable,
+                                                             SeahorseExporterType type);
+
+guint       seahorse_exportable_export_to_directory_wait    (GList *objects,
+                                                             const gchar *directory,
+                                                             GError **error);
+
+guint       seahorse_exportable_export_to_text_wait         (GList *objects,
+                                                             gpointer *data,
+                                                             gsize *size,
+                                                             GError **error);
+
+guint       seahorse_exportable_export_to_prompt_wait       (GList *objects,
+                                                             GtkWindow *parent,
+                                                             GError **error);
+
+gboolean    seahorse_exportable_prompt                      (GList *exporters,
+                                                             GtkWindow *parent,
+                                                             gchar **directory,
+                                                             GFile **file,
+                                                             SeahorseExporter **exporter);
+
+#endif /* __SEAHORSE_EXPORTABLE_H__ */
diff --git a/libseahorse/seahorse-exporter.c b/libseahorse/seahorse-exporter.c
new file mode 100644
index 0000000..a0f89cf
--- /dev/null
+++ b/libseahorse/seahorse-exporter.c
@@ -0,0 +1,273 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2004,2005 Stefan Walter
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU 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.
+ */
+
+#include "config.h"
+
+#include "seahorse-exporter.h"
+#include "seahorse-util.h"
+
+typedef SeahorseExporterIface SeahorseExporterInterface;
+
+G_DEFINE_INTERFACE (SeahorseExporter, seahorse_exporter, G_TYPE_OBJECT);
+
+static void
+seahorse_exporter_default_init (SeahorseExporterIface *iface)
+{
+	static gboolean initialized = FALSE;
+	if (!initialized) {
+		g_object_interface_install_property (iface,
+		                g_param_spec_string ("filename", "File name", "File name",
+		                                     NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+		g_object_interface_install_property (iface,
+		                g_param_spec_string ("content-type", "Content type", "Content type",
+		                                     NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+		g_object_interface_install_property (iface,
+		                g_param_spec_object ("file-filter", "File filter", "File chooser filter",
+		                                     GTK_TYPE_FILE_FILTER, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+		initialized = TRUE;
+	}
+}
+
+gchar *
+seahorse_exporter_get_filename (SeahorseExporter *exporter)
+{
+	gchar *filename = NULL;
+
+	g_return_val_if_fail (SEAHORSE_IS_EXPORTER (exporter), NULL);
+
+	g_object_get (exporter, "filename", &filename, NULL);
+	return filename;
+}
+
+gchar *
+seahorse_exporter_get_content_type (SeahorseExporter *exporter)
+{
+	gchar *content_type = NULL;
+
+	g_return_val_if_fail (SEAHORSE_IS_EXPORTER (exporter), NULL);
+
+	g_object_get (exporter, "content-type", &content_type, NULL);
+	return content_type;
+}
+
+GtkFileFilter *
+seahorse_exporter_get_file_filter (SeahorseExporter *exporter)
+{
+	GtkFileFilter *filter = NULL;
+
+	g_return_val_if_fail (SEAHORSE_IS_EXPORTER (exporter), NULL);
+
+	g_object_get (exporter, "file-filter", &filter, NULL);
+	return filter;
+}
+
+GList *
+seahorse_exporter_get_objects (SeahorseExporter *exporter)
+{
+	SeahorseExporterIface *iface;
+
+	g_return_val_if_fail (SEAHORSE_IS_EXPORTER (exporter), NULL);
+
+	iface = SEAHORSE_EXPORTER_GET_INTERFACE (exporter);
+	g_return_val_if_fail (iface->get_objects != NULL, NULL);
+
+	return (iface->get_objects) (exporter);
+}
+
+gboolean
+seahorse_exporter_add_object (SeahorseExporter *exporter,
+                              GObject *object)
+{
+	SeahorseExporterIface *iface;
+
+	g_return_val_if_fail (SEAHORSE_IS_EXPORTER (exporter), FALSE);
+	g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+
+	iface = SEAHORSE_EXPORTER_GET_INTERFACE (exporter);
+	g_return_val_if_fail (iface->add_object != NULL, FALSE);
+
+	return (iface->add_object) (exporter, object);
+}
+
+void
+seahorse_exporter_export_async (SeahorseExporter *exporter,
+                                GCancellable *cancellable,
+                                GAsyncReadyCallback callback,
+                                gpointer user_data)
+{
+	SeahorseExporterIface *iface;
+
+	g_return_if_fail (SEAHORSE_IS_EXPORTER (exporter));
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+	iface = SEAHORSE_EXPORTER_GET_INTERFACE (exporter);
+	g_return_if_fail (iface->export_async != NULL);
+
+	(iface->export_async) (exporter, cancellable, callback, user_data);
+}
+
+gpointer
+seahorse_exporter_export_finish (SeahorseExporter *exporter,
+                                 GAsyncResult *result,
+                                 gsize *size,
+                                 GError **error)
+{
+	SeahorseExporterIface *iface;
+
+	g_return_val_if_fail (SEAHORSE_IS_EXPORTER (exporter), NULL);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+	g_return_val_if_fail (size != NULL, NULL);
+
+	iface = SEAHORSE_EXPORTER_GET_INTERFACE (exporter);
+	g_return_val_if_fail (iface->export_finish != NULL, NULL);
+
+	return (iface->export_finish) (exporter, result, size, error);
+}
+
+typedef struct {
+	GCancellable *cancellable;
+	gboolean overwrite;
+	guint unique;
+	gpointer data;
+	gsize size;
+	GFile *file;
+} FileClosure;
+
+static void
+file_closure_free (gpointer data)
+{
+	FileClosure *closure = data;
+	g_clear_object (&closure->cancellable);
+	g_object_unref (closure->file);
+	g_free (closure->data);
+	g_free (closure);
+}
+
+static void
+on_export_replace (GObject *source,
+                   GAsyncResult *result,
+                   gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	FileClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	GError *error = NULL;
+	GFile *file;
+
+	if (g_file_replace_contents_finish (closure->file, result, NULL, &error)) {
+		g_simple_async_result_complete (res);
+
+	/* Not overwriting, and the file existed, try another file name */
+	} else if (!closure->overwrite && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WRONG_ETAG)) {
+		file = seahorse_util_file_increment_unique (closure->file, &closure->unique);
+
+		g_file_replace_contents_async (file, closure->data, closure->size, "invalid-etag",
+		                               FALSE, G_FILE_CREATE_PRIVATE, closure->cancellable,
+		                               on_export_replace, g_object_ref (res));
+
+		g_clear_error (&error);
+		g_object_unref (file);
+
+	} else {
+		g_simple_async_result_take_error (res, error);
+		g_simple_async_result_complete (res);
+	}
+
+	g_object_unref (res);
+}
+
+static void
+on_export_complete (GObject *source,
+                    GAsyncResult *result,
+                    gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	FileClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	GError *error = NULL;
+
+	closure->data = seahorse_exporter_export_finish (SEAHORSE_EXPORTER (source),
+	                                                 result, &closure->size, &error);
+	if (error == NULL) {
+		/*
+		 * When not trying to overwrite we pass an invalid etag. This way
+		 * if the file exists, it will not match the etag, and we'll be
+		 * able to detect it and try another file name.
+		 */
+		g_file_replace_contents_async (closure->file, closure->data,
+		                               closure->size, closure->overwrite ? NULL : "invalid-etag",
+		                               FALSE, G_FILE_CREATE_PRIVATE, closure->cancellable,
+		                               on_export_replace, g_object_ref (res));
+	} else {
+		g_simple_async_result_take_error (res, error);
+		g_simple_async_result_complete (res);
+	}
+
+	g_object_unref (res);
+}
+
+void
+seahorse_exporter_export_to_file_async (SeahorseExporter *exporter,
+                                        GFile *file,
+                                        gboolean overwrite,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data)
+{
+	GSimpleAsyncResult *res;
+	FileClosure *closure;
+
+	g_return_if_fail (SEAHORSE_IS_EXPORTER (exporter));
+	g_return_if_fail (G_IS_FILE (file));
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+	res = g_simple_async_result_new (G_OBJECT (exporter), callback, user_data,
+	                                 seahorse_exporter_export_to_file_async);
+	closure = g_new0 (FileClosure, 1);
+	closure->file = g_object_ref (file);
+	closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+	closure->overwrite = overwrite;
+	g_simple_async_result_set_op_res_gpointer (res, closure, file_closure_free);
+
+	seahorse_exporter_export_async (exporter, cancellable, on_export_complete,
+	                                g_object_ref (res));
+
+	g_object_unref (res);
+}
+
+gboolean
+seahorse_exporter_export_to_file_finish (SeahorseExporter *exporter,
+                                         GAsyncResult *result,
+                                         GError **error)
+{
+	g_return_val_if_fail (SEAHORSE_IS_EXPORTER (exporter), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (exporter),
+	                      seahorse_exporter_export_to_file_async), FALSE);
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return FALSE;
+
+	return TRUE;
+}
diff --git a/libseahorse/seahorse-exporter.h b/libseahorse/seahorse-exporter.h
new file mode 100644
index 0000000..698545d
--- /dev/null
+++ b/libseahorse/seahorse-exporter.h
@@ -0,0 +1,101 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Exporter, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#ifndef __SEAHORSE_EXPORTER_H__
+#define __SEAHORSE_EXPORTER_H__
+
+#include "seahorse-types.h"
+
+#include <gio/gio.h>
+
+#include <gtk/gtk.h>
+
+typedef enum {
+	SEAHORSE_EXPORTER_ANY,
+	SEAHORSE_EXPORTER_TEXTUAL
+} SeahorseExporterType;
+
+#define SEAHORSE_TYPE_EXPORTER                (seahorse_exporter_get_type ())
+#define SEAHORSE_EXPORTER(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAHORSE_TYPE_EXPORTER, SeahorseExporter))
+#define SEAHORSE_IS_EXPORTER(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAHORSE_TYPE_EXPORTER))
+#define SEAHORSE_EXPORTER_GET_INTERFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), SEAHORSE_TYPE_EXPORTER, SeahorseExporterIface))
+
+typedef struct _SeahorseExporter SeahorseExporter;
+typedef struct _SeahorseExporterIface SeahorseExporterIface;
+
+struct _SeahorseExporterIface {
+	GTypeInterface parent;
+
+	/* virtual methods ------------------------------------------------- */
+
+	GList *         (* get_objects)              (SeahorseExporter *exporter);
+
+	gboolean        (* add_object)               (SeahorseExporter *exporter,
+	                                              GObject *object);
+
+	void            (* export_async)             (SeahorseExporter *exporter,
+	                                              GCancellable *cancellable,
+	                                              GAsyncReadyCallback callback,
+	                                              gpointer user_data);
+
+	gpointer        (* export_finish)            (SeahorseExporter *exporter,
+	                                              GAsyncResult *result,
+	                                              gsize *size,
+	                                              GError **error);
+};
+
+GType            seahorse_exporter_get_type             (void) G_GNUC_CONST;
+
+gchar *          seahorse_exporter_get_filename         (SeahorseExporter *exporter);
+
+gchar *          seahorse_exporter_get_content_type     (SeahorseExporter *exporter);
+
+GtkFileFilter *  seahorse_exporter_get_file_filter      (SeahorseExporter *exporter);
+
+GList *          seahorse_exporter_get_objects          (SeahorseExporter *exporter);
+
+gboolean         seahorse_exporter_add_object           (SeahorseExporter *exporter,
+                                                         GObject *object);
+
+void             seahorse_exporter_export_async         (SeahorseExporter *exporter,
+                                                         GCancellable *cancellable,
+                                                         GAsyncReadyCallback callback,
+                                                         gpointer user_data);
+
+gpointer         seahorse_exporter_export_finish        (SeahorseExporter *exporter,
+                                                         GAsyncResult *result,
+                                                         gsize *size,
+                                                         GError **error);
+
+void            seahorse_exporter_export_to_file_async  (SeahorseExporter *exporter,
+                                                         GFile *file,
+                                                         gboolean overwrite,
+                                                         GCancellable *cancellable,
+                                                         GAsyncReadyCallback callback,
+                                                         gpointer user_data);
+
+gboolean        seahorse_exporter_export_to_file_finish (SeahorseExporter *exporter,
+                                                         GAsyncResult *result,
+                                                         GError **error);
+
+#endif /* __SEAHORSE_EXPORTER_H__ */
diff --git a/libseahorse/seahorse-key-manager-store.c b/libseahorse/seahorse-key-manager-store.c
index 1e7ccf2..670e1b1 100644
--- a/libseahorse/seahorse-key-manager-store.c
+++ b/libseahorse/seahorse-key-manager-store.c
@@ -23,6 +23,7 @@
 
 #include "config.h"
 
+#include "seahorse-exportable.h"
 #include "seahorse-place.h"
 #include "seahorse-util.h"
 
@@ -348,70 +349,47 @@ typedef struct {
 } export_keys_to_output_closure;
 
 static gboolean
-export_to_text (SeahorseKeyManagerStore *skstore,
+export_to_text (SeahorseKeyManagerStore *self,
                 GtkSelectionData *selection_data)
 {
-	GOutputStream *output;
-	gboolean ret = FALSE;
+	gpointer output;
+	gsize size;
+	gboolean ret;
+	guint count;
 
-	g_return_val_if_fail (skstore->priv->drag_objects, FALSE);
+	g_return_val_if_fail (self->priv->drag_objects, FALSE);
 	seahorse_debug ("exporting to text");
 
-	output = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
-	g_return_val_if_fail (output, FALSE);
+	count = seahorse_exportable_export_to_text_wait (self->priv->drag_objects,
+	                                                 &output, &size, &self->priv->drag_error);
 
-	ret = seahorse_place_export_auto_wait (skstore->priv->drag_objects, output,
-	                                       &skstore->priv->drag_error) &&
-	      g_output_stream_close (output, NULL, &skstore->priv->drag_error);
+	/* TODO: Need to print status if only partially exported */
 
-	if (ret) {
+	if (count > 0) {
 		seahorse_debug ("setting selection text");
-		gtk_selection_data_set_text (selection_data,
-		                             g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (output)),
-		                             g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (output)));
+		gtk_selection_data_set_text (selection_data, output, size);
+		ret = TRUE;
+	} else if (self->priv->drag_error) {
+		g_message ("error occurred on export: %s", self->priv->drag_error->message);
+		ret = FALSE;
 	} else {
-		g_message ("error occurred on export: %s",
-		           skstore->priv->drag_error && skstore->priv->drag_error->message ?
-		                      skstore->priv->drag_error->message : "");
+		g_message ("no objects exported");
+		ret = FALSE;
 	}
 
-	g_object_unref (output);
+	g_free (output);
 	return ret;
 }
 
 static gboolean
-export_to_filename (SeahorseKeyManagerStore *skstore, const gchar *filename)
+export_to_directory (SeahorseKeyManagerStore *self,
+                     const gchar *directory)
 {
-	GOutputStream *output;
-	gboolean ret;
-	gchar *uri;
-	GFile *file;
-	GList *keys;
-
-	seahorse_debug ("exporting to %s", filename);
-
-	ret = FALSE;
-	g_return_val_if_fail (skstore->priv->drag_objects, FALSE);
-	keys = g_list_copy (skstore->priv->drag_objects);
-
-	uri = seahorse_util_uri_unique (filename);
-
-	/* Create output file */
-	file = g_file_new_for_uri (uri);
-	g_free (uri);
-	output = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE,
-	                                          NULL, &skstore->priv->drag_error));
-	g_object_unref (file);
+	seahorse_debug ("exporting to %s", directory);
 
-	if (output) {
-		/* This modifies and frees keys */
-		ret = seahorse_place_export_auto_wait (keys, output, &skstore->priv->drag_error) &&
-		      g_output_stream_close (output, NULL, &skstore->priv->drag_error);
-
-		g_object_unref (output);
-	}
-
-	return ret;
+	return seahorse_exportable_export_to_directory_wait (self->priv->drag_objects,
+	                                                     directory,
+	                                                     &self->priv->drag_error);
 }
 
 static gboolean
@@ -455,22 +433,10 @@ drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *sel
 static void
 drag_end (GtkWidget *widget, GdkDragContext *context, SeahorseKeyManagerStore *skstore)
 {
-	gchar *filename, *name;
-
 	seahorse_debug ("drag_end -->");
 
-	if (skstore->priv->drag_destination && !skstore->priv->drag_error) {
-		g_return_if_fail (skstore->priv->drag_objects);
-
-		name = seahorse_util_filename_for_objects (skstore->priv->drag_objects);
-		g_return_if_fail (name);
-
-		filename = g_build_filename (skstore->priv->drag_destination, name, NULL);
-		g_free (name);
-
-		export_to_filename (skstore, filename);
-		g_free (filename);
-	}
+	if (skstore->priv->drag_destination && !skstore->priv->drag_error)
+		export_to_directory (skstore, skstore->priv->drag_destination);
 
 	if (skstore->priv->drag_error) {
 		seahorse_util_show_error (widget, _("Couldn't export keys"),
diff --git a/libseahorse/seahorse-place.c b/libseahorse/seahorse-place.c
index aaec3aa..1bbe98a 100644
--- a/libseahorse/seahorse-place.c
+++ b/libseahorse/seahorse-place.c
@@ -109,171 +109,3 @@ seahorse_place_import_finish (SeahorsePlace *place,
 	g_return_val_if_fail (SEAHORSE_PLACE_GET_INTERFACE (place)->import_finish, NULL);
 	return SEAHORSE_PLACE_GET_INTERFACE (place)->import_finish (place, result, error);
 }
-
-void
-seahorse_place_export_async (SeahorsePlace *place,
-                             GList *objects,
-                             GOutputStream *output,
-                             GCancellable *cancellable,
-                             GAsyncReadyCallback callback,
-                             gpointer user_data)
-{
-	SeahorsePlaceIface *iface;
-
-	g_return_if_fail (SEAHORSE_IS_PLACE (place));
-	g_return_if_fail (G_IS_OUTPUT_STREAM (output));
-	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
-
-	iface = SEAHORSE_PLACE_GET_INTERFACE (place);
-	g_return_if_fail (iface->export_async != NULL);
-
-	iface->export_async (place, objects, output,
-	                     cancellable, callback, user_data);
-}
-
-GOutputStream *
-seahorse_place_export_finish (SeahorsePlace *place,
-                               GAsyncResult *result,
-                               GError **error)
-{
-	SeahorsePlaceIface *iface;
-
-	g_return_val_if_fail (SEAHORSE_IS_PLACE (place), NULL);
-	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
-	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-	iface = SEAHORSE_PLACE_GET_INTERFACE (place);
-	g_return_val_if_fail (iface->export_async != NULL, NULL);
-	g_return_val_if_fail (iface->export_finish, NULL);
-	return (iface->export_finish) (place, result, error);
-}
-
-typedef struct {
-	GOutputStream *output;
-	guint exports;
-} export_auto_closure;
-
-static void
-export_auto_free (gpointer data)
-{
-	export_auto_closure *closure = data;
-	g_object_unref (closure->output);
-	g_free (closure);
-}
-
-static void
-on_export_auto_complete (GObject *place,
-                         GAsyncResult *result,
-                         gpointer user_data)
-{
-	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	export_auto_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
-	GError *error = NULL;
-
-	if (!seahorse_place_export_finish (SEAHORSE_PLACE (place), result, &error))
-		g_simple_async_result_take_error (res, error);
-
-	g_assert (closure->exports > 0);
-	closure->exports--;
-
-	if (closure->exports == 0)
-		g_simple_async_result_complete (res);
-
-	g_object_unref (res);
-}
-
-void
-seahorse_place_export_auto_async (GList *objects,
-                                  GOutputStream *output,
-                                  GCancellable *cancellable,
-                                  GAsyncReadyCallback callback,
-                                  gpointer user_data)
-{
-	GSimpleAsyncResult *res;
-	export_auto_closure *closure;
-	SeahorsePlace *place;
-	GObject *object;
-	GList *next;
-
-	res = g_simple_async_result_new (NULL, callback, user_data,
-	                                 seahorse_place_export_auto_async);
-	closure = g_new0 (export_auto_closure, 1);
-	closure->output = g_object_ref (output);
-	g_simple_async_result_set_op_res_gpointer (res, closure, export_auto_free);
-
-	/* Sort by object place */
-	objects = g_list_copy (objects);
-	objects = seahorse_util_objects_sort_by_place (objects);
-
-	while (objects) {
-
-		/* Break off one set (same place) */
-		next = seahorse_util_objects_splice_by_place (objects);
-
-		g_assert (G_IS_OBJECT (objects->data));
-		object = G_OBJECT (objects->data);
-
-		/* Export from this object place */
-		place = NULL;
-		g_object_get (object, "place", &place, NULL);
-		g_return_if_fail (place != NULL);
-
-		/* We pass our own data object, to which data is appended */
-		seahorse_place_export_async (place, objects, output, cancellable,
-		                              on_export_auto_complete, g_object_ref (res));
-		closure->exports++;
-
-		g_list_free (objects);
-		objects = next;
-	}
-
-	if (closure->exports == 0)
-		g_simple_async_result_complete_in_idle (res);
-
-	g_object_unref (res);
-}
-
-GOutputStream *
-seahorse_place_export_auto_finish (GAsyncResult *result,
-                                   GError **error)
-{
-	export_auto_closure *closure;
-
-	g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL,
-	                      seahorse_place_export_auto_async), NULL);
-
-	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
-		return NULL;
-
-	closure = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
-	return closure->output;
-
-}
-
-static void
-on_export_auto_wait_complete (GObject *place,
-                              GAsyncResult *result,
-                              gpointer user_data)
-{
-	GAsyncResult **ret = user_data;
-	*ret = g_object_ref (result);
-}
-
-gboolean
-seahorse_place_export_auto_wait (GList *objects,
-                                 GOutputStream *output,
-                                 GError **error)
-{
-	GAsyncResult *result = NULL;
-	gboolean ret;
-
-	seahorse_place_export_auto_async (objects, output, NULL,
-	                                   on_export_auto_wait_complete,
-	                                   &result);
-
-	seahorse_util_wait_until (result != NULL);
-
-	ret = seahorse_place_export_auto_finish (result, error) != NULL;
-	g_object_unref (result);
-	return ret;
-}
diff --git a/libseahorse/seahorse-place.h b/libseahorse/seahorse-place.h
index 5b05d3e..c0e72da 100644
--- a/libseahorse/seahorse-place.h
+++ b/libseahorse/seahorse-place.h
@@ -58,25 +58,12 @@ struct _SeahorsePlaceIface {
 	GList *         (*import_finish)             (SeahorsePlace *place,
 	                                              GAsyncResult *result,
 	                                              GError **error);
-
-	void            (*export_async)              (SeahorsePlace *place,
-	                                              GList *objects,
-	                                              GOutputStream *output,
-	                                              GCancellable *cancellable,
-	                                              GAsyncReadyCallback callback,
-	                                              gpointer user_data);
-
-	GOutputStream * (*export_finish)             (SeahorsePlace *place,
-	                                              GAsyncResult *result,
-	                                              GError **error);
 };
 
 GType            seahorse_place_get_type             (void) G_GNUC_CONST;
 
 GtkActionGroup * seahorse_place_get_actions          (SeahorsePlace *self);
 
-/* Method helper functions ------------------------------------------- */
-
 void             seahorse_place_import_async         (SeahorsePlace *place,
                                                       GInputStream *input,
                                                       GCancellable *cancellable,
@@ -87,28 +74,4 @@ GList *          seahorse_place_import_finish        (SeahorsePlace *place,
                                                       GAsyncResult *result,
                                                       GError **error);
 
-void             seahorse_place_export_async         (SeahorsePlace *place,
-                                                      GList *objects,
-                                                      GOutputStream *output,
-                                                       GCancellable *cancellable,
-                                                       GAsyncReadyCallback callback,
-                                                       gpointer user_data);
-
-GOutputStream *  seahorse_place_export_finish        (SeahorsePlace *place,
-                                                       GAsyncResult *result,
-                                                       GError **error);
-
-void             seahorse_place_export_auto_async    (GList *objects,
-                                                       GOutputStream *output,
-                                                       GCancellable *cancellable,
-                                                       GAsyncReadyCallback callback,
-                                                       gpointer user_data);
-
-GOutputStream *  seahorse_place_export_auto_finish   (GAsyncResult *result,
-                                                       GError **error);
-
-gboolean         seahorse_place_export_auto_wait     (GList *objects,
-                                                       GOutputStream *output,
-                                                       GError **error);
-
 #endif /* __SEAHORSE_PLACE_H__ */
diff --git a/libseahorse/seahorse-util.c b/libseahorse/seahorse-util.c
index 55c99b3..ee9b3a6 100644
--- a/libseahorse/seahorse-util.c
+++ b/libseahorse/seahorse-util.c
@@ -52,8 +52,6 @@
 
 #include <sys/types.h>
 
-static const gchar *bad_filename_chars = "/\\<>|";
-
 /**
  * seahorse_util_show_error:
  * @parent: The parent widget. Can be NULL
@@ -372,41 +370,6 @@ seahorse_util_printf_fd (int fd, const char* fmt, ...)
     return ret;
 }
 
-/**
- * seahorse_util_filename_for_objects:
- * @objects: A list of objects
- *
- * If the single object has a nickname, this will be returned (with .asc attached)
- * If there are multiple objects, "Multiple Keys.asc" will be returned.
- * Single objects default to "Key Data.asc".
- * Results are internationalized
- *
- * Returns: NULL on error, the filename else. The returned string should be
- * freed with #g_free when no longer needed.
- */
-gchar*      
-seahorse_util_filename_for_objects (GList *objects)
-{
-	const gchar *name = NULL;
-	gchar *filename;
-
-	g_return_val_if_fail (g_list_length (objects) > 0, NULL);
-
-	if (g_list_length (objects) == 1) {
-		if (SEAHORSE_IS_OBJECT (objects->data))
-			name = seahorse_object_get_nickname (SEAHORSE_OBJECT (objects->data));
-		if (name == NULL)
-			name = _("Key Data");
-	} else {
-		name = _("Multiple Keys");
-	}
-    
-	filename = g_strconcat (name, SEAHORSE_EXT_ASC, NULL);
-	g_strstrip (filename);
-	g_strdelimit (filename, bad_filename_chars, '_');
-	return filename;
-}
-
 /** 
  * seahorse_util_uri_get_last:
  * @uri: The uri to parse
@@ -435,86 +398,48 @@ seahorse_util_uri_get_last (const gchar* uri)
     return t;
 }    
 
-/**
- * seahorse_util_uri_exists:
- * @uri: The uri to check
- * 
- * Verify whether a given uri exists or not.
- * 
- * Returns: FALSE if it does not exist, TRUE else
- **/
-gboolean
-seahorse_util_uri_exists (const gchar* uri)
+GFile *
+seahorse_util_file_increment_unique (GFile *file,
+                                     guint *state)
 {
-	GFile *file;
-	gboolean exists;
+	GFile *result;
+	gchar *suffix;
+	gchar *prefix;
+	gchar *uri_try;
+	gchar *x;
+	guint len;
 
-	file = g_file_new_for_uri (uri);
-	g_return_val_if_fail (file, FALSE);
-	
-	exists = g_file_query_exists (file, NULL);
-	g_object_unref (file);
-	
-	return exists;
-}       
-    
-/**
- * seahorse_util_uri_unique:
- * @uri: The uri to guarantee is unique
- * 
- * Creates a URI based on @uri that does not exist.
- * A simple numbering scheme is used to create new
- * URIs. Not meant for temp file creation.
- * 
- * Returns: Newly allocated unique URI.
- **/
-gchar*
-seahorse_util_uri_unique (const gchar* uri)
-{
-    gchar* suffix;
-    gchar* prefix;
-    gchar* uri_try;
-    gchar* x;
-    guint len;
-    int i;
-    
-    /* Simple when doesn't exist */
-    if (!seahorse_util_uri_exists (uri))
-        return g_strdup (uri);
-    
-    prefix = g_strdup (uri);
-    len = strlen (prefix); 
-    
-    /* Always take off a slash at end */
-    g_return_val_if_fail (len > 1, g_strdup (uri));
-    if (prefix[len - 1] == '/')
-        prefix[len - 1] = 0;
-            
-    /* Split into prefix and suffix */
-    suffix = strrchr (prefix, '.');
-    x = strrchr(uri, '/');
-    if (suffix == NULL || (x != NULL && suffix < x)) {
-        suffix = g_strdup ("");
-    } else {
-        x = suffix;
-        suffix = g_strdup (suffix);
-        *x = 0;
-    }                
-        
-    for (i = 1; i < 1000; i++) {
-       
-        uri_try = g_strdup_printf ("%s-%d%s", prefix, i, suffix);
-       
-        if (!seahorse_util_uri_exists (uri_try))
-            break;
-        
-        g_free (uri_try);
-        uri_try = NULL;
-    }
-        
-    g_free (suffix);    
-    g_free (prefix);
-    return uri_try ? uri_try : g_strdup (uri);       
+	g_return_val_if_fail (G_IS_FILE (file), NULL);
+	g_return_val_if_fail (state != NULL, NULL);
+
+	prefix = g_file_get_uri (file);
+	len = strlen (prefix);
+
+	g_return_val_if_fail (len > 0, NULL);
+
+	/* Always take off a slash at end */
+	if (prefix[len - 1] == '/')
+		prefix[len - 1] = 0;
+
+	/* Split into prefix and suffix */
+	suffix = strrchr (prefix, '.');
+	x = strrchr (prefix, '/');
+	if (suffix == NULL || (x != NULL && suffix < x)) {
+		suffix = g_strdup ("");
+	} else {
+		x = suffix;
+		suffix = g_strdup (suffix);
+		*x = 0;
+	}
+
+	++(*state);
+	uri_try = g_strdup_printf ("%s-%u%s", prefix, *state, suffix);
+	g_free (suffix);
+	g_free (prefix);
+
+	result = g_file_new_for_uri (uri_try);
+	g_free (uri_try);
+	return result;
 }
 
 /**
@@ -705,136 +630,6 @@ seahorse_util_chooser_show_key_files (GtkDialog *dialog)
 }
 
 /**
- * seahorse_util_chooser_show_archive_files:
- * @dialog: the dialog to add the filter for
- *
- * Adds a archive file filter and a "All files" filter. The archive filter
- * is used.
- *
- */
-void
-seahorse_util_chooser_show_archive_files (GtkDialog *dialog)
-{
-    GtkFileFilter* filter;
-    int i;
-    
-    static const char *archive_mime_type[] = {
-        "application/x-ar",
-        "application/x-arj",
-        "application/x-bzip",
-        "application/x-bzip-compressed-tar",
-        "application/x-cd-image",
-        "application/x-compress",
-        "application/x-compressed-tar",
-        "application/x-gzip",
-        "application/x-java-archive",
-        "application/x-jar",
-        "application/x-lha",
-        "application/x-lzop",
-        "application/x-rar",
-        "application/x-rar-compressed",
-        "application/x-tar",
-        "application/x-zoo",
-        "application/zip",
-        "application/x-7zip"
-    };
-    
-    filter = gtk_file_filter_new ();
-    gtk_file_filter_set_name (filter, _("Archive files"));
-    for (i = 0; i < G_N_ELEMENTS (archive_mime_type); i++)
-        gtk_file_filter_add_mime_type (filter, archive_mime_type[i]);
-    gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);    
-    gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
-
-    filter = gtk_file_filter_new ();
-    gtk_file_filter_set_name (filter, _("All files"));
-    gtk_file_filter_add_pattern (filter, "*");    
-    gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);   
-}
-
-/**
- * seahorse_util_chooser_set_filename_full:
- * @dialog: The dialog to pre set the name
- * @objects: generate the file name from this object
- *
- *
- */
-void
-seahorse_util_chooser_set_filename_full (GtkDialog *dialog, GList *objects)
-{
-    gchar *t = NULL;
-    
-    if (g_list_length (objects) > 0) {
-        t = seahorse_util_filename_for_objects (objects);
-        gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), t);
-        g_free (t);
-    }
-}
-
-/**
- * seahorse_util_chooser_set_filename:
- * @dialog: set the dialog for this
- * @object: The object to use for the filename. #GObject
- *
- */
-void
-seahorse_util_chooser_set_filename (GtkDialog *dialog,
-                                    GObject *object)
-{
-	GList *objects = g_list_append (NULL, object);
-	seahorse_util_chooser_set_filename_full (dialog, objects);
-	g_list_free (objects);
-}
-
-/**
- * seahorse_util_chooser_save_prompt:
- * @dialog: save dialog to show
- *
- * If the selected file already exists, a confirmation dialog will be displayed
- *
- * Returns: the uri of the chosen file or NULL
- */
-gchar*      
-seahorse_util_chooser_save_prompt (GtkDialog *dialog)
-{
-    GtkWidget* edlg;
-    gchar *uri = NULL;
-    
-    while (gtk_dialog_run (dialog) == GTK_RESPONSE_ACCEPT) {
-     
-        uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER (dialog));
-
-        if (uri == NULL)
-            continue;
-            
-        if (seahorse_util_uri_exists (uri)) {
-
-            edlg = gtk_message_dialog_new_with_markup (GTK_WINDOW (dialog),
-                        GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION,
-                        GTK_BUTTONS_NONE, _("<b>A file already exists with this name.</b>\n\nDo you want to replace it with a new file?"));
-            gtk_dialog_add_buttons (GTK_DIALOG (edlg), 
-                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-                        _("_Replace"), GTK_RESPONSE_ACCEPT, NULL);
-
-            gtk_dialog_set_default_response (GTK_DIALOG (edlg), GTK_RESPONSE_CANCEL);
-                  
-            if (gtk_dialog_run (GTK_DIALOG (edlg)) != GTK_RESPONSE_ACCEPT) {
-                g_free (uri);
-                uri = NULL;
-            }
-                
-            gtk_widget_destroy (edlg);
-        } 
-             
-        if (uri != NULL)
-            break;
-    }
-  
-    gtk_widget_destroy (GTK_WIDGET (dialog));
-    return uri;
-}
-
-/**
  * seahorse_util_chooser_open_prompt:
  * @dialog: open dialog to display
  *
diff --git a/libseahorse/seahorse-util.h b/libseahorse/seahorse-util.h
index f6d6d79..cdcf04f 100644
--- a/libseahorse/seahorse-util.h
+++ b/libseahorse/seahorse-util.h
@@ -38,8 +38,7 @@ const AvahiPoll* seahorse_util_dns_sd_get_poll (void);
 
 typedef guint64 SeahorseVersion;
 
-#define SEAHORSE_EXT_ASC ".asc"
-#define SEAHORSE_EXT_GPG ".gpg"
+#define SEAHORSE_BAD_FILENAME_CHARS  "/\\<>|:?;"
 
 gchar*      seahorse_util_get_date_string           (const time_t time);
 gchar*      seahorse_util_get_display_date_string   (const time_t time);
@@ -71,12 +70,9 @@ gboolean    seahorse_util_print_fd          (int fd,
 
 gboolean    seahorse_util_printf_fd         (int fd, 
                                              const char* data, ...);
-                             
-gchar*      seahorse_util_filename_for_objects (GList *objects);
-                                             
-gboolean    seahorse_util_uri_exists        (const gchar* uri);
 
-gchar*      seahorse_util_uri_unique        (const gchar* uri);
+GFile *     seahorse_util_file_increment_unique     (GFile *file,
+                                                     guint *state);
 
 const gchar* seahorse_util_uri_get_last     (const gchar* uri);
 
@@ -99,18 +95,9 @@ GtkDialog*  seahorse_util_chooser_save_new              (const gchar *title,
 
 void        seahorse_util_chooser_show_key_files        (GtkDialog *dialog);
 
-void        seahorse_util_chooser_show_archive_files    (GtkDialog *dialog);
-
-void        seahorse_util_chooser_set_filename_full     (GtkDialog *dialog, 
-                                                         GList *objects);
-
-void        seahorse_util_chooser_set_filename          (GtkDialog *dialog, 
-                                                         GObject *object);
 
 gchar*      seahorse_util_chooser_open_prompt           (GtkDialog *dialog);
 
-gchar*      seahorse_util_chooser_save_prompt           (GtkDialog *dialog);
-
 GList *     seahorse_util_objects_sort_by_place         (GList *objects);
 
 GList *     seahorse_util_objects_splice_by_place       (GList *objects);
diff --git a/libseahorse/seahorse-viewer.c b/libseahorse/seahorse-viewer.c
index 7c5834c..bb42111 100644
--- a/libseahorse/seahorse-viewer.c
+++ b/libseahorse/seahorse-viewer.c
@@ -25,6 +25,7 @@
 #include "seahorse-action.h"
 #include "seahorse-actions.h"
 #include "seahorse-backend.h"
+#include "seahorse-exportable.h"
 #include "seahorse-object.h"
 #include "seahorse-preferences.h"
 #include "seahorse-progress.h"
@@ -223,158 +224,84 @@ on_properties_place (GtkAction *action,
 	g_list_free (objects);
 }
 
-static const GtkActionEntry UI_ENTRIES[] = {
-
-	/* Top menu items */
-	{ "file-menu", NULL, N_("_File") },
-	{ "edit-menu", NULL, N_("_Edit") },
-	/*Translators: This text refers to deleting an item from its type's backing store*/
-	{ "edit-delete", GTK_STOCK_DELETE, N_("_Delete"), NULL,
-	  N_("Delete selected items"), G_CALLBACK (on_object_delete) },
-	{ "properties-object", GTK_STOCK_PROPERTIES, NULL, NULL,
-	  N_("Show the properties of this item"), G_CALLBACK (on_properties_object) },
-	{ "properties-place", GTK_STOCK_PROPERTIES, NULL, NULL,
-	  N_("Show the properties of this place"), G_CALLBACK (on_properties_place) },
-	{ "app-preferences", GTK_STOCK_PREFERENCES, N_("Prefere_nces"), NULL,
-	  N_("Change preferences for this program"), G_CALLBACK (on_app_preferences) },
-	{ "view-menu", NULL, N_("_View") },
-	{ "help-menu", NULL, N_("_Help") },
-	{ "app-about", GTK_STOCK_ABOUT, NULL, NULL,
-	  N_("About this program"), G_CALLBACK (on_app_about) },
-	{ "help-show", GTK_STOCK_HELP, N_("_Contents"), "F1",
-	  N_("Show Seahorse help"), G_CALLBACK (on_help_show) }
-};
-
-#if 0
-static void
-on_file_export_completed (GObject *source,
-                          GAsyncResult *result,
-                          gpointer user_data)
-{
-	SeahorseViewer* self = SEAHORSE_VIEWER (user_data);
-	GError *error = NULL;
-
-	if (!seahorse_place_export_auto_finish (result, &error))
-		seahorse_util_handle_error (&error, seahorse_viewer_get_window (self),
-		                            _("Couldn't export keys"));
-
-	g_object_unref (self);
-}
-
 static void
 on_key_export_file (GtkAction* action, SeahorseViewer* self)
 {
 	GError *error = NULL;
+	GtkWindow *window;
 	GList *objects;
-	GtkDialog *dialog;
-	gchar *uri, *unesc_uri;
 
 	g_return_if_fail (SEAHORSE_IS_VIEWER (self));
 	g_return_if_fail (GTK_IS_ACTION (action));
 
+	window = seahorse_viewer_get_window (self);
 	objects = seahorse_viewer_get_selected_objects (self);
-	objects = objects_prune_non_exportable (objects);
-	if (objects == NULL)
-		return;
-
-	dialog = seahorse_util_chooser_save_new (_("Export public key"),
-	                                         seahorse_viewer_get_window (self));
-	seahorse_util_chooser_show_key_files (dialog);
-	seahorse_util_chooser_set_filename_full (dialog, objects);
-	uri = seahorse_util_chooser_save_prompt (dialog);
-	if (uri != NULL) {
-		GFile* file;
-		GOutputStream* output;
-		GCancellable *cancellable;
-
-		file = g_file_new_for_uri (uri);
-		output = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, 0, NULL, &error));
-		if (output == NULL) {
-		    unesc_uri = g_uri_unescape_string (seahorse_util_uri_get_last (uri), NULL);
-			seahorse_util_handle_error (&error, NULL, _ ("Couldn't export key to \"%s\""),
-			                            unesc_uri, NULL);
-			g_free (unesc_uri);
-		} else {
-			cancellable = g_cancellable_new ();
-			seahorse_place_export_auto_async (objects, output, cancellable,
-			                                  on_file_export_completed, g_object_ref (self));
-			seahorse_progress_show (cancellable, _("Exporting keys"), TRUE);
-			g_object_unref (cancellable);
-		}
-
+	seahorse_exportable_export_to_prompt_wait (objects, window, &error);
+	g_list_free (objects);
 
-		g_object_unref (file);
-		g_object_unref (output);
-		g_free (uri);
-	}
+	/* TODO: message if only partially exported */
 
-	g_list_free (objects);
+	if (error != NULL)
+		seahorse_util_handle_error (&error, window, _("Couldn't export keys"));
 }
 
 static void
-on_copy_export_complete (GObject *source,
-                         GAsyncResult *result,
-                         gpointer user_data)
+on_key_export_clipboard (GtkAction* action,
+                         SeahorseViewer* self)
 {
-	SeahorseViewer *self = SEAHORSE_VIEWER (user_data);
-	GOutputStream* output;
+	GList* objects;
+	gpointer output;
+	gsize size;
 	GError *error = NULL;
-	const gchar* text;
-	guint size;
 	GdkAtom atom;
 	GtkClipboard* board;
 
-	output = seahorse_place_export_auto_finish (result, &error);
-	if (error != NULL) {
-		seahorse_util_handle_error (&error, seahorse_viewer_get_window (self),
-		                            _("Couldn't retrieve data from key server"));
-		return;
-	}
-
-	text = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (output));
-	size = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (output));
-
-	atom = gdk_atom_intern ("CLIPBOARD", FALSE);
-	board = gtk_clipboard_get (atom);
-	gtk_clipboard_set_text (board, text, (gint)size);
-
-	g_object_unref (self);
-}
-
-static void
-on_key_export_clipboard (GtkAction* action, SeahorseViewer* self)
-{
-	GList* objects;
-	GOutputStream* output;
-	GCancellable *cancellable;
-
 	g_return_if_fail (SEAHORSE_IS_VIEWER (self));
 	g_return_if_fail (GTK_IS_ACTION (action));
 
 	objects = seahorse_viewer_get_selected_objects (self);
-	objects = objects_prune_non_exportable (objects);
-	if (objects == NULL)
-		return;
+	seahorse_exportable_export_to_text_wait (objects, &output, &size, &error);
+	g_list_free (objects);
 
-	output = G_OUTPUT_STREAM (g_memory_output_stream_new (NULL, 0, g_realloc, g_free));
+	/* TODO: Print message if only partially exported */
 
-	cancellable = g_cancellable_new ();
-	seahorse_place_export_auto_async (objects, output, cancellable,
-	                                  on_copy_export_complete, g_object_ref (self));
-	seahorse_progress_show (cancellable, _ ("Retrieving keys"), TRUE);
-	g_object_unref (cancellable);
+	if (error == NULL) {
+		atom = gdk_atom_intern ("CLIPBOARD", FALSE);
+		board = gtk_clipboard_get (atom);
+		gtk_clipboard_set_text (board, output, (gint)size);
+	} else {
+		seahorse_util_handle_error (&error, seahorse_viewer_get_window (self),
+		                            _("Couldn't export data"));
+	}
 
-	g_list_free (objects);
-	g_object_unref (output);
+	g_free (output);
 }
 
-static const GtkActionEntry EXPORT_ENTRIES[] = {
+static const GtkActionEntry UI_ENTRIES[] = {
+
+	/* Top menu items */
+	{ "file-menu", NULL, N_("_File") },
 	{ "file-export", GTK_STOCK_SAVE_AS, N_("E_xport..."), NULL,
 	  N_("Export to a file"), G_CALLBACK (on_key_export_file) },
+	{ "edit-menu", NULL, N_("_Edit") },
+	/*Translators: This text refers to deleting an item from its type's backing store*/
 	{ "edit-export-clipboard", GTK_STOCK_COPY, NULL, "<control>C",
-	  N_("Copy to the clipboard"), G_CALLBACK (on_key_export_clipboard) }
+	  N_("Copy to the clipboard"), G_CALLBACK (on_key_export_clipboard) },
+	{ "edit-delete", GTK_STOCK_DELETE, N_("_Delete"), NULL,
+	  N_("Delete selected items"), G_CALLBACK (on_object_delete) },
+	{ "properties-object", GTK_STOCK_PROPERTIES, NULL, NULL,
+	  N_("Show the properties of this item"), G_CALLBACK (on_properties_object) },
+	{ "properties-place", GTK_STOCK_PROPERTIES, NULL, NULL,
+	  N_("Show the properties of this place"), G_CALLBACK (on_properties_place) },
+	{ "app-preferences", GTK_STOCK_PREFERENCES, N_("Prefere_nces"), NULL,
+	  N_("Change preferences for this program"), G_CALLBACK (on_app_preferences) },
+	{ "view-menu", NULL, N_("_View") },
+	{ "help-menu", NULL, N_("_Help") },
+	{ "app-about", GTK_STOCK_ABOUT, NULL, NULL,
+	  N_("About this program"), G_CALLBACK (on_app_about) },
+	{ "help-show", GTK_STOCK_HELP, N_("_Contents"), "F1",
+	  N_("Show Seahorse help"), G_CALLBACK (on_help_show) }
 };
-#endif
 
 static void
 on_ui_manager_pre_activate (GtkUIManager *ui_manager,
diff --git a/pgp/Makefile.am b/pgp/Makefile.am
index 39813d0..d7c260f 100644
--- a/pgp/Makefile.am
+++ b/pgp/Makefile.am
@@ -46,6 +46,7 @@ libseahorse_pgp_la_SOURCES = \
 	seahorse-gpgme-dialogs.h \
 	seahorse-gpgme-data.c seahorse-gpgme-data.h \
 	seahorse-gpgme-expires.c \
+	seahorse-gpgme-exporter.c seahorse-gpgme-exporter.h \
 	seahorse-gpgme-generate.c \
 	seahorse-gpgme-key.c seahorse-gpgme-key.h \
 	seahorse-gpgme-key-op.c seahorse-gpgme-key-op.h \
diff --git a/pgp/seahorse-gpg-op.c b/pgp/seahorse-gpg-op.c
index ee3daee..f395df9 100644
--- a/pgp/seahorse-gpg-op.c
+++ b/pgp/seahorse-gpg-op.c
@@ -61,34 +61,33 @@ execute_gpg_command (gpgme_ctx_t ctx, const gchar *args, gchar **std_out,
     return gerr;
 }
 
-gpgme_error_t        
-seahorse_gpg_op_export_secret  (gpgme_ctx_t ctx, const char *pattern,
-                                gpgme_data_t keydata)
+gpgme_error_t
+seahorse_gpg_op_export_secret (gpgme_ctx_t ctx,
+                               const gchar **patterns,
+                               gpgme_data_t keydata)
 {
-    gchar *output = NULL;
-    gpgme_error_t err;
-    gchar *args;
-    
-    g_return_val_if_fail (pattern != NULL, GPG_E (GPG_ERR_INV_VALUE));
-    
-    /* 
-     * We have to use armor mode, because otherwise below 
-     * string stuff doesn't work 
-     */
-    args = g_strdup_printf ("--armor --export-secret-key '%s'", 
-                            pattern);
-    
-    err = execute_gpg_command (ctx, args, &output, NULL);
-    g_free (args);
-    
-    if (!GPG_IS_OK (err))
-        return err;
-    
-    if (gpgme_data_write (keydata, output, strlen (output)) == -1)
-        return GPG_E (GPG_ERR_GENERAL);
-    
-    g_free (output);
-    return GPG_OK;
+	gchar *output = NULL;
+	gpgme_error_t gerr;
+	gchar *args;
+	gsize i;
+
+	g_return_val_if_fail (patterns != NULL, GPG_E (GPG_ERR_INV_VALUE));
+
+	for (i = 0; patterns[i] != NULL; i++) {
+		args = g_strdup_printf ("--armor --export-secret-key '%s'", patterns[i]);
+		gerr = execute_gpg_command (ctx, args, &output, NULL);
+		g_free (args);
+
+		if (!GPG_IS_OK (gerr))
+			return gerr;
+
+		if (gpgme_data_write (keydata, output, strlen (output)) == -1)
+			return GPG_E (GPG_ERR_GENERAL);
+
+		g_free (output);
+	}
+
+	return GPG_OK;
 }
 
 gpgme_error_t 
diff --git a/pgp/seahorse-gpg-op.h b/pgp/seahorse-gpg-op.h
index 311eb91..fb24d91 100644
--- a/pgp/seahorse-gpg-op.h
+++ b/pgp/seahorse-gpg-op.h
@@ -27,7 +27,7 @@
 #include <gpgme.h>
 
 gpgme_error_t seahorse_gpg_op_export_secret  (gpgme_ctx_t ctx, 
-                                              const char *pattern,
+                                              const gchar **patterns,
                                               gpgme_data_t keydata);
 
 gpgme_error_t seahorse_gpg_op_num_uids       (gpgme_ctx_t ctx, 
diff --git a/pgp/seahorse-gpgme-exporter.c b/pgp/seahorse-gpgme-exporter.c
new file mode 100644
index 0000000..73328fd
--- /dev/null
+++ b/pgp/seahorse-gpgme-exporter.c
@@ -0,0 +1,445 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU 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 "seahorse-gpgme.h"
+#include "seahorse-gpgme-data.h"
+#include "seahorse-gpgme-exporter.h"
+#include "seahorse-gpgme-key.h"
+#include "seahorse-gpgme-keyring.h"
+#include "seahorse-gpg-op.h"
+#include "seahorse-progress.h"
+
+#include "seahorse-exporter.h"
+#include "seahorse-object.h"
+#include "seahorse-util.h"
+
+#include <glib/gi18n.h>
+
+#include <string.h>
+
+#define SEAHORSE_GPGME_EXPORTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SEAHORSE_TYPE_GPGME_EXPORTER, SeahorseGpgmeExporterClass))
+#define SEAHORSE_IS_GPGME_EXPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAHORSE_TYPE_GPGME_EXPORTER))
+#define SEAHORSE_GPGME_EXPORTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAHORSE_TYPE_GPGME_EXPORTER, SeahorseGpgmeExporterClass))
+
+typedef struct _SeahorseGpgmeExporterClass SeahorseGpgmeExporterClass;
+
+struct _SeahorseGpgmeExporter {
+	GObject parent;
+	GList *objects;
+	gboolean armor;
+	gboolean secret;
+};
+
+struct _SeahorseGpgmeExporterClass {
+	GObjectClass parent_class;
+};
+
+enum {
+	PROP_0,
+	PROP_FILENAME,
+	PROP_CONTENT_TYPE,
+	PROP_FILE_FILTER,
+	PROP_ARMOR,
+	PROP_SECRET
+};
+
+static void   seahorse_gpgme_exporter_iface_init    (SeahorseExporterIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (SeahorseGpgmeExporter, seahorse_gpgme_exporter, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_EXPORTER, seahorse_gpgme_exporter_iface_init);
+);
+
+static gchar *
+calc_filename (SeahorseGpgmeExporter *self)
+{
+	const gchar *basename = NULL;
+	gchar *filename;
+
+	g_return_val_if_fail (self->objects, NULL);
+
+	/* Multiple objects */
+	if (self->objects->next)
+		basename = _("Multiple Keys");
+	else if (self->objects->data)
+		basename = seahorse_object_get_nickname (self->objects->data);
+	if (basename == NULL)
+		basename = _("Key Data");
+
+	if (self->armor)
+		filename = g_strconcat (basename, ".asc", NULL);
+	else
+		filename = g_strconcat (basename, ".pgp", NULL);
+	g_strstrip (filename);
+	g_strdelimit (filename, SEAHORSE_BAD_FILENAME_CHARS, '_');
+	return filename;
+}
+
+static const gchar *
+calc_content_type (SeahorseGpgmeExporter *self)
+{
+	if (self->armor)
+		return "application/pgp-keys";
+	else
+		return "application/pgp-keys+armor";
+}
+
+static GtkFileFilter *
+calc_file_filter (SeahorseGpgmeExporter *self)
+{
+	GtkFileFilter *filter = gtk_file_filter_new ();
+
+	if (self->armor) {
+		gtk_file_filter_set_name (filter, _("Armored PGP keys"));
+		gtk_file_filter_add_mime_type (filter, "application/pgp-keys+armor");
+		gtk_file_filter_add_pattern (filter, "*.asc");
+	} else {
+		gtk_file_filter_set_name (filter, _("PGP keys"));
+		gtk_file_filter_add_mime_type (filter, "application/pgp-keys");
+		gtk_file_filter_add_pattern (filter, "*.pgp");
+		gtk_file_filter_add_pattern (filter, "*.gpg");
+	}
+
+	return filter;
+}
+
+static void
+seahorse_gpgme_exporter_init (SeahorseGpgmeExporter *self)
+{
+
+}
+
+static void
+seahorse_gpgme_exporter_get_property (GObject *object,
+                                      guint prop_id,
+                                      GValue *value,
+                                      GParamSpec *pspec)
+{
+	SeahorseGpgmeExporter *self = SEAHORSE_GPGME_EXPORTER (object);
+
+	switch (prop_id) {
+	case PROP_FILENAME:
+		g_value_take_string (value, calc_filename (self));
+		break;
+	case PROP_CONTENT_TYPE:
+		g_value_set_string (value, calc_content_type (self));
+		break;
+	case PROP_FILE_FILTER:
+		g_value_take_object (value, calc_file_filter (self));
+		break;
+	case PROP_ARMOR:
+		g_value_set_boolean (value, self->armor);
+		break;
+	case PROP_SECRET:
+		g_value_set_boolean (value, self->secret);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+seahorse_gpgme_exporter_set_property (GObject *object,
+                                      guint prop_id,
+                                      const GValue *value,
+                                      GParamSpec *pspec)
+{
+	SeahorseGpgmeExporter *self = SEAHORSE_GPGME_EXPORTER (object);
+
+	switch (prop_id) {
+	case PROP_ARMOR:
+		self->armor = g_value_get_boolean (value);
+		break;
+	case PROP_SECRET:
+		self->secret = g_value_get_boolean (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+seahorse_gpgme_exporter_finalize (GObject *obj)
+{
+	SeahorseGpgmeExporter *self = SEAHORSE_GPGME_EXPORTER (obj);
+
+	g_list_free_full (self->objects, g_object_unref);
+
+	G_OBJECT_CLASS (seahorse_gpgme_exporter_parent_class)->finalize (obj);
+}
+
+static void
+seahorse_gpgme_exporter_class_init (SeahorseGpgmeExporterClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->finalize = seahorse_gpgme_exporter_finalize;
+	gobject_class->set_property = seahorse_gpgme_exporter_set_property;
+	gobject_class->get_property = seahorse_gpgme_exporter_get_property;
+
+	g_object_class_override_property (gobject_class, PROP_FILENAME, "filename");
+	g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type");
+	g_object_class_override_property (gobject_class, PROP_FILE_FILTER, "file-filter");
+
+	g_object_class_install_property (gobject_class, PROP_ARMOR,
+	           g_param_spec_boolean ("armor", "Armor", "Armor encoding",
+	                                 FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property (gobject_class, PROP_SECRET,
+	           g_param_spec_boolean ("secret", "Secret", "Secret key export",
+	                                 FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static GList *
+seahorse_gpgme_exporter_get_objects (SeahorseExporter *exporter)
+{
+	SeahorseGpgmeExporter *self = SEAHORSE_GPGME_EXPORTER (exporter);
+	return self->objects;
+}
+
+static gboolean
+seahorse_gpgme_exporter_add_object (SeahorseExporter *exporter,
+                                    GObject *object)
+{
+	SeahorseGpgmeExporter *self = SEAHORSE_GPGME_EXPORTER (exporter);
+	SeahorseGpgmeKey *key;
+
+	if (SEAHORSE_IS_GPGME_KEY (object)) {
+		key = SEAHORSE_GPGME_KEY (object);
+		if (self->secret && seahorse_gpgme_key_get_private (key) == NULL)
+			return FALSE;
+
+		self->objects = g_list_append (self->objects, g_object_ref (key));
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+typedef struct {
+	GPtrArray *keyids;
+	gint at;
+	gpgme_data_t data;
+	gpgme_ctx_t gctx;
+	GMemoryOutputStream *output;
+	GCancellable *cancellable;
+	gulong cancelled_sig;
+} GpgmeExportClosure;
+
+static void
+gpgme_export_closure_free (gpointer data)
+{
+	GpgmeExportClosure *closure = data;
+	g_cancellable_disconnect (closure->cancellable, closure->cancelled_sig);
+	g_clear_object (&closure->cancellable);
+	gpgme_data_release (closure->data);
+	if (closure->gctx)
+		gpgme_release (closure->gctx);
+	g_ptr_array_free (closure->keyids, TRUE);
+	g_object_unref (closure->output);
+	g_free (closure);
+}
+
+
+static gboolean
+on_keyring_export_complete (gpgme_error_t gerr,
+                            gpointer user_data)
+{
+	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
+	GpgmeExportClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	GError *error = NULL;
+
+	if (seahorse_gpgme_propagate_error (gerr, &error)) {
+		g_simple_async_result_take_error (res, error);
+		g_simple_async_result_complete (res);
+		return FALSE; /* don't call again */
+	}
+
+	if (closure->at >= 0)
+		seahorse_progress_end (closure->cancellable,
+		                       closure->keyids->pdata[closure->at]);
+
+	g_assert (closure->at < (gint)closure->keyids->len);
+	closure->at++;
+
+	if (closure->at == closure->keyids->len) {
+		g_simple_async_result_complete (res);
+		return FALSE; /* don't run this again */
+	}
+
+	/* Do the next key in the list */
+	gerr = gpgme_op_export_start (closure->gctx, closure->keyids->pdata[closure->at],
+	                              0, closure->data);
+
+	if (seahorse_gpgme_propagate_error (gerr, &error)) {
+		g_simple_async_result_take_error (res, error);
+		g_simple_async_result_complete (res);
+		return FALSE; /* don't run this again */
+	}
+
+	seahorse_progress_begin (closure->cancellable,
+	                         closure->keyids->pdata[closure->at]);
+	return TRUE; /* call this source again */
+}
+
+static void
+seahorse_gpgme_exporter_export_async (SeahorseExporter *exporter,
+                                      GCancellable *cancellable,
+                                      GAsyncReadyCallback callback,
+                                      gpointer user_data)
+{
+	SeahorseGpgmeExporter *self = SEAHORSE_GPGME_EXPORTER (exporter);
+	GSimpleAsyncResult *res;
+	GpgmeExportClosure *closure;
+	GError *error = NULL;
+	gpgme_error_t gerr = 0;
+	SeahorsePgpKey *key;
+	gchar *keyid;
+	GSource *gsource;
+	GList *l;
+
+	res = g_simple_async_result_new (G_OBJECT (exporter), callback, user_data,
+	                                 seahorse_gpgme_exporter_export_async);
+	closure = g_new0 (GpgmeExportClosure, 1);
+	closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+	closure->gctx = seahorse_gpgme_keyring_new_context (&gerr);
+	closure->output = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new (NULL, 0, g_realloc, g_free));
+	closure->data = seahorse_gpgme_data_output (G_OUTPUT_STREAM (closure->output));
+	closure->keyids = g_ptr_array_new_with_free_func (g_free);
+	closure->at = -1;
+	g_simple_async_result_set_op_res_gpointer (res, closure, gpgme_export_closure_free);
+
+	if (seahorse_gpgme_propagate_error (gerr, &error)) {
+		g_simple_async_result_take_error (res, error);
+		g_simple_async_result_complete_in_idle (res);
+		g_object_unref (res);
+		return;
+	}
+
+	gpgme_set_armor (closure->gctx, self->armor);
+
+	for (l = self->objects; l != NULL; l = g_list_next (l)) {
+		key = SEAHORSE_PGP_KEY (l->data);
+
+		/* Building list */
+		keyid = g_strdup (seahorse_pgp_key_get_keyid (key));
+		seahorse_progress_prep (closure->cancellable, keyid, NULL);
+		g_ptr_array_add (closure->keyids, keyid);
+	}
+
+	if (self->secret) {
+		g_return_if_fail (self->armor == TRUE);
+		g_ptr_array_add (closure->keyids, NULL);
+		gerr = seahorse_gpg_op_export_secret (closure->gctx, (const gchar **)closure->keyids->pdata,
+		                                      closure->data);
+		if (seahorse_gpgme_propagate_error (gerr, &error))
+			g_simple_async_result_take_error (res, error);
+		g_simple_async_result_complete_in_idle (res);
+		g_object_unref (res);
+		return;
+	}
+
+	gsource = seahorse_gpgme_gsource_new (closure->gctx, cancellable);
+	g_source_set_callback (gsource, (GSourceFunc)on_keyring_export_complete,
+	                       g_object_ref (res), g_object_unref);
+
+	/* Get things started */
+	if (on_keyring_export_complete (0, res))
+		g_source_attach (gsource, g_main_context_default ());
+
+	g_source_unref (gsource);
+	g_object_unref (res);
+}
+
+static gpointer
+seahorse_gpgme_exporter_export_finish (SeahorseExporter *exporter,
+                                       GAsyncResult *result,
+                                       gsize *size,
+                                       GError **error)
+{
+	GpgmeExportClosure *closure;
+
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (exporter),
+	                      seahorse_gpgme_exporter_export_async), NULL);
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+
+	closure = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+	g_output_stream_close (G_OUTPUT_STREAM (closure->output), NULL, NULL);
+	*size = g_memory_output_stream_get_data_size (closure->output);
+	return g_memory_output_stream_steal_data (closure->output);
+}
+
+static void
+seahorse_gpgme_exporter_iface_init (SeahorseExporterIface *iface)
+{
+	iface->add_object = seahorse_gpgme_exporter_add_object;
+	iface->export_async = seahorse_gpgme_exporter_export_async;
+	iface->export_finish = seahorse_gpgme_exporter_export_finish;
+	iface->get_objects = seahorse_gpgme_exporter_get_objects;
+}
+
+SeahorseExporter *
+seahorse_gpgme_exporter_new (GObject *object,
+                             gboolean armor,
+                             gboolean secret)
+{
+	SeahorseExporter *exporter;
+
+	g_return_val_if_fail (secret == FALSE || armor == TRUE, NULL);
+
+	exporter = g_object_new (SEAHORSE_TYPE_GPGME_EXPORTER,
+	                         "armor", armor,
+	                         "secret", secret,
+	                         NULL);
+
+	if (!seahorse_exporter_add_object (exporter, object))
+		g_return_val_if_reached (NULL);
+
+	return exporter;
+}
+
+
+SeahorseExporter *
+seahorse_gpgme_exporter_new_multiple (GList *keys,
+                                      gboolean armor)
+{
+	SeahorseExporter *exporter;
+	GList *l;
+
+	exporter = g_object_new (SEAHORSE_TYPE_GPGME_EXPORTER,
+	                         "armor", armor,
+	                         "secret", FALSE,
+	                         NULL);
+
+	for (l = keys; l != NULL; l = g_list_next (l)) {
+		if (!seahorse_exporter_add_object (exporter, l->data))
+			g_return_val_if_reached (NULL);
+	}
+
+	return exporter;
+}
diff --git a/pgp/seahorse-gpgme-exporter.h b/pgp/seahorse-gpgme-exporter.h
new file mode 100644
index 0000000..1b3c7ba
--- /dev/null
+++ b/pgp/seahorse-gpgme-exporter.h
@@ -0,0 +1,47 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU 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 __SEAHORSE_GPGME_EXPORTER_H__
+#define __SEAHORSE_GPGME_EXPORTER_H__
+
+#include <glib-object.h>
+
+#include "seahorse-exporter.h"
+
+#define SEAHORSE_TYPE_GPGME_EXPORTER            (seahorse_gpgme_exporter_get_type ())
+#define SEAHORSE_GPGME_EXPORTER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAHORSE_TYPE_GPGME_EXPORTER, SeahorseGpgmeExporter))
+#define SEAHORSE_IS_GPGME_EXPORTER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAHORSE_TYPE_GPGME_EXPORTER))
+
+typedef struct _SeahorseGpgmeExporter SeahorseGpgmeExporter;
+
+GType                     seahorse_gpgme_exporter_get_type     (void) G_GNUC_CONST;
+
+SeahorseExporter *        seahorse_gpgme_exporter_new          (GObject *object,
+                                                                gboolean armor,
+                                                                gboolean secret);
+
+SeahorseExporter *        seahorse_gpgme_exporter_new_multiple (GList *keys,
+                                                                gboolean armor);
+
+
+#endif /* __SEAHORSE_GPGME_EXPORTER_H__ */
diff --git a/pgp/seahorse-gpgme-key.c b/pgp/seahorse-gpgme-key.c
index f2875e6..b90c0d6 100644
--- a/pgp/seahorse-gpgme-key.c
+++ b/pgp/seahorse-gpgme-key.c
@@ -21,24 +21,26 @@
  */
 #include "config.h"
 
-#include <string.h>
-
-#include <glib/gi18n.h>
+#include "seahorse-gpgme.h"
+#include "seahorse-gpgme-exporter.h"
+#include "seahorse-gpgme-key-op.h"
+#include "seahorse-gpgme-photo.h"
+#include "seahorse-gpgme-keyring.h"
+#include "seahorse-gpgme-uid.h"
+#include "seahorse-pgp-actions.h"
+#include "seahorse-pgp-backend.h"
+#include "seahorse-pgp-key.h"
 
+#include "seahorse-exportable.h"
 #include "seahorse-icons.h"
 #include "seahorse-predicate.h"
 #include "seahorse-object-list.h"
 #include "seahorse-place.h"
 #include "seahorse-util.h"
 
-#include "seahorse-pgp-actions.h"
-#include "seahorse-pgp-backend.h"
-#include "pgp/seahorse-pgp-key.h"
-#include "pgp/seahorse-gpgme.h"
-#include "pgp/seahorse-gpgme-key-op.h"
-#include "pgp/seahorse-gpgme-photo.h"
-#include "pgp/seahorse-gpgme-keyring.h"
-#include "pgp/seahorse-gpgme-uid.h"
+#include <glib/gi18n.h>
+
+#include <string.h>
 
 enum {
 	PROP_0,
@@ -48,7 +50,11 @@ enum {
 	PROP_TRUST
 };
 
-G_DEFINE_TYPE (SeahorseGpgmeKey, seahorse_gpgme_key, SEAHORSE_TYPE_PGP_KEY);
+static void       seahorse_gpgme_key_exportable_iface      (SeahorseExportableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (SeahorseGpgmeKey, seahorse_gpgme_key, SEAHORSE_TYPE_PGP_KEY,
+                         G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_EXPORTABLE, seahorse_gpgme_key_exportable_iface);
+);
 
 struct _SeahorseGpgmeKeyPrivate {
 	gpgme_key_t pubkey;   		/* The public key */
@@ -544,6 +550,24 @@ seahorse_gpgme_key_class_init (SeahorseGpgmeKeyClass *klass)
         g_object_class_override_property (gobject_class, PROP_TRUST, "trust");
 }
 
+static GList *
+seahorse_gpgme_key_create_exporters (SeahorseExportable *exportable,
+                                     SeahorseExporterType type)
+{
+	GList *result = NULL;
+
+	if (type != SEAHORSE_EXPORTER_TEXTUAL)
+		result = g_list_append (result, seahorse_gpgme_exporter_new (G_OBJECT (exportable), FALSE, FALSE));
+	result = g_list_append (result, seahorse_gpgme_exporter_new (G_OBJECT (exportable), TRUE, FALSE));
+
+	return result;
+}
+
+static void
+seahorse_gpgme_key_exportable_iface (SeahorseExportableIface *iface)
+{
+	iface->create_exporters = seahorse_gpgme_key_create_exporters;
+}
 
 /* -----------------------------------------------------------------------------
  * PUBLIC 
diff --git a/pgp/seahorse-gpgme-keyring.c b/pgp/seahorse-gpgme-keyring.c
index ee8652d..2d0b144 100644
--- a/pgp/seahorse-gpgme-keyring.c
+++ b/pgp/seahorse-gpgme-keyring.c
@@ -729,154 +729,6 @@ seahorse_gpgme_keyring_import_finish (SeahorsePlace *place,
 	return results;
 }
 
-typedef struct {
-	GPtrArray *keyids;
-	gint at;
-	gpgme_data_t data;
-	gpgme_ctx_t gctx;
-	GOutputStream *output;
-	GCancellable *cancellable;
-	gulong cancelled_sig;
-} keyring_export_closure;
-
-static void
-keyring_export_free (gpointer data)
-{
-	keyring_export_closure *closure = data;
-	g_cancellable_disconnect (closure->cancellable, closure->cancelled_sig);
-	g_clear_object (&closure->cancellable);
-	gpgme_data_release (closure->data);
-	if (closure->gctx)
-		gpgme_release (closure->gctx);
-	g_ptr_array_free (closure->keyids, TRUE);
-	g_object_unref (closure->output);
-	g_free (closure);
-}
-
-static gboolean
-on_keyring_export_complete (gpgme_error_t gerr,
-                           gpointer user_data)
-{
-	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	keyring_export_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
-	GError *error = NULL;
-
-	if (seahorse_gpgme_propagate_error (gerr, &error)) {
-		g_simple_async_result_take_error (res, error);
-		g_simple_async_result_complete (res);
-		return FALSE; /* don't call again */
-	}
-
-	if (closure->at >= 0)
-		seahorse_progress_end (closure->cancellable,
-		                       closure->keyids->pdata[closure->at]);
-
-	g_assert (closure->at < (gint)closure->keyids->len);
-	closure->at++;
-
-	if (closure->at == closure->keyids->len) {
-		g_simple_async_result_complete (res);
-		return FALSE; /* don't run this again */
-	}
-
-	/* Do the next key in the list */
-	gerr = gpgme_op_export_start (closure->gctx, closure->keyids->pdata[closure->at],
-	                              0, closure->data);
-
-	if (seahorse_gpgme_propagate_error (gerr, &error)) {
-		g_simple_async_result_take_error (res, error);
-		g_simple_async_result_complete (res);
-		return FALSE; /* don't run this again */
-	}
-
-	seahorse_progress_begin (closure->cancellable,
-	                         closure->keyids->pdata[closure->at]);
-	return TRUE; /* call this source again */
-}
-
-static void
-seahorse_gpgme_keyring_export_async (SeahorsePlace *place,
-                                     GList *objects,
-                                     GOutputStream *output,
-                                     GCancellable *cancellable,
-                                     GAsyncReadyCallback callback,
-                                     gpointer user_data)
-{
-	GSimpleAsyncResult *res;
-	keyring_export_closure *closure;
-	GError *error = NULL;
-	gpgme_error_t gerr = 0;
-	SeahorsePgpKey *key;
-	gchar *keyid;
-	GSource *gsource;
-	GList *l;
-
-	res = g_simple_async_result_new (G_OBJECT (place), callback, user_data,
-	                                 seahorse_gpgme_keyring_export_async);
-	closure = g_new0 (keyring_export_closure, 1);
-	closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-	closure->gctx = seahorse_gpgme_keyring_new_context (&gerr);
-	closure->data = seahorse_gpgme_data_output (output);
-	closure->keyids = g_ptr_array_new_with_free_func (g_free);
-	closure->output = g_object_ref (output);
-	closure->at = -1;
-	g_simple_async_result_set_op_res_gpointer (res, closure, keyring_export_free);
-
-	if (seahorse_gpgme_propagate_error (gerr, &error)) {
-		g_simple_async_result_take_error (res, error);
-		g_simple_async_result_complete_in_idle (res);
-		g_object_unref (res);
-		return;
-	}
-
-	gpgme_set_armor (closure->gctx, TRUE);
-	gpgme_set_textmode (closure->gctx, TRUE);
-
-	for (l = objects; l != NULL; l = g_list_next (l)) {
-
-		/* Ignore PGP Uids */
-		if (SEAHORSE_IS_PGP_UID (l->data))
-			continue;
-
-		g_return_if_fail (SEAHORSE_IS_PGP_KEY (l->data));
-		key = SEAHORSE_PGP_KEY (l->data);
-		g_return_if_fail (seahorse_object_get_place (SEAHORSE_OBJECT (key)) == place);
-
-		/* Building list */
-		keyid = g_strdup (seahorse_pgp_key_get_keyid (key));
-		seahorse_progress_prep (closure->cancellable, keyid, NULL);
-		g_ptr_array_add (closure->keyids, keyid);
-	}
-
-	gsource = seahorse_gpgme_gsource_new (closure->gctx, cancellable);
-	g_source_set_callback (gsource, (GSourceFunc)on_keyring_export_complete,
-	                       g_object_ref (res), g_object_unref);
-
-	/* Get things started */
-	if (on_keyring_export_complete (0, res))
-		g_source_attach (gsource, g_main_context_default ());
-
-	g_source_unref (gsource);
-	g_object_unref (res);
-}
-
-static GOutputStream *
-seahorse_gpgme_keyring_export_finish (SeahorsePlace *place,
-                                      GAsyncResult *result,
-                                      GError **error)
-{
-	keyring_export_closure *closure;
-
-	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (place),
-	                      seahorse_gpgme_keyring_export_async), NULL);
-
-	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
-		return NULL;
-
-	closure = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
-	return closure->output;
-}
-
 static gboolean
 scheduled_refresh (gpointer user_data)
 {
@@ -901,7 +753,7 @@ monitor_gpg_homedir (GFileMonitor *handle, GFile *file, GFile *other_file,
 	    event_type == G_FILE_MONITOR_EVENT_CREATED) {
 
 		name = g_file_get_basename (file);
-		if (g_str_has_suffix (name, SEAHORSE_EXT_GPG)) {
+		if (g_str_has_suffix (name, ".gpg")) {
 			if (self->pv->scheduled_refresh == 0) {
 				seahorse_debug ("scheduling refresh event due to file changes");
 				self->pv->scheduled_refresh = g_timeout_add (500, scheduled_refresh, self);
@@ -1048,8 +900,6 @@ seahorse_gpgme_keyring_place_iface (SeahorsePlaceIface *iface)
 {
 	iface->import_async = seahorse_gpgme_keyring_import_async;
 	iface->import_finish = seahorse_gpgme_keyring_import_finish;
-	iface->export_async = seahorse_gpgme_keyring_export_async;
-	iface->export_finish = seahorse_gpgme_keyring_export_finish;
 }
 
 static guint
diff --git a/pgp/seahorse-hkp-source.c b/pgp/seahorse-hkp-source.c
index 2aeaf28..2004ad3 100644
--- a/pgp/seahorse-hkp-source.c
+++ b/pgp/seahorse-hkp-source.c
@@ -926,19 +926,20 @@ seahorse_hkp_source_import_finish (SeahorsePlace *place,
 
 typedef struct {
 	SeahorseHKPSource *source;
-	GOutputStream *output;
+	GString *data;
 	GCancellable *cancellable;
 	gulong cancelled_sig;
 	SoupSession *session;
 	gint requests;
-} source_export_closure;
+} ExportClosure;
 
 static void
-source_export_free (gpointer data)
+export_closure_free (gpointer data)
 {
-	source_export_closure *closure = data;
+	ExportClosure *closure = data;
 	g_object_unref (closure->source);
-	g_object_unref (closure->output);
+	if (closure->data)
+		g_string_free (closure->data, TRUE);
 	g_cancellable_disconnect (closure->cancellable, closure->cancelled_sig);
 	g_clear_object (&closure->cancellable);
 	g_object_unref (closure->session);
@@ -951,14 +952,12 @@ on_export_message_complete (SoupSession *session,
                             gpointer user_data)
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	source_export_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	ExportClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
 	GError *error = NULL;
 	const gchar *start;
 	const gchar *end;
 	const gchar *text;
-	gboolean ret;
 	guint len;
-	gsize written;
 
 	seahorse_progress_end (closure->cancellable, message);
 
@@ -980,16 +979,8 @@ on_export_message_complete (SoupSession *session,
 		if (!detect_key (text, len, &start, &end))
 			break;
 
-		ret = g_output_stream_write_all (closure->output, start, end - start, &written, NULL, &error) &&
-		      g_output_stream_write_all (closure->output, "\n", 1, &written, NULL, &error) &&
-		      g_output_stream_flush (closure->output, NULL, &error);
-
-		if (!ret) {
-			g_simple_async_result_take_error (res, error);
-			g_simple_async_result_complete_in_idle (res);
-			g_object_unref (res);
-			return;
-		}
+		g_string_append_len (closure->data, start, end - start);
+		g_string_append_c (closure->data, '\n');
 	}
 
 	g_assert (closure->requests > 0);
@@ -1011,13 +1002,12 @@ on_export_message_complete (SoupSession *session,
 static void
 seahorse_hkp_source_export_async (SeahorseServerSource *source,
                                   GList *keyids,
-                                  GOutputStream *output,
                                   GCancellable *cancellable,
                                   GAsyncReadyCallback callback,
                                   gpointer user_data)
 {
 	SeahorseHKPSource *self = SEAHORSE_HKP_SOURCE (source);
-	source_export_closure *closure;
+	ExportClosure *closure;
 	GSimpleAsyncResult *res;
 	SoupMessage *message;
 	SoupURI *uri;
@@ -1029,12 +1019,12 @@ seahorse_hkp_source_export_async (SeahorseServerSource *source,
 
 	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
 	                                 seahorse_hkp_source_export_async);
-	closure = g_new0 (source_export_closure, 1);
+	closure = g_new0 (ExportClosure, 1);
 	closure->source = g_object_ref (self);
-	closure->output = g_object_ref (output);
+	closure->data = g_string_sized_new (1024);
 	closure->session = create_hkp_soup_session ();
 	closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-	g_simple_async_result_set_op_res_gpointer (res, closure, source_export_free);
+	g_simple_async_result_set_op_res_gpointer (res, closure, export_closure_free);
 
 	if (g_list_length (keyids) == 0) {
 		g_simple_async_result_complete_in_idle (res);
@@ -1085,13 +1075,16 @@ seahorse_hkp_source_export_async (SeahorseServerSource *source,
 	g_object_unref (res);
 }
 
-static GOutputStream *
+static gpointer
 seahorse_hkp_source_export_finish (SeahorseServerSource *source,
                                    GAsyncResult *result,
+                                   gsize *size,
                                    GError **error)
 {
-	source_export_closure *closure;
+	ExportClosure *closure;
+	gpointer output;
 
+	g_return_val_if_fail (size != NULL, NULL);
 	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (source),
 	                      seahorse_hkp_source_export_async), NULL);
 
@@ -1099,7 +1092,10 @@ seahorse_hkp_source_export_finish (SeahorseServerSource *source,
 		return NULL;
 
 	closure = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
-	return closure->output;
+	*size = closure->data->len;
+	output = g_string_free (closure->data, FALSE);
+	closure->data = NULL;
+	return output;
 }
 
 /**
diff --git a/pgp/seahorse-ldap-source.c b/pgp/seahorse-ldap-source.c
index df30b83..38d3d0c 100644
--- a/pgp/seahorse-ldap-source.c
+++ b/pgp/seahorse-ldap-source.c
@@ -1204,17 +1204,18 @@ seahorse_ldap_source_import_finish (SeahorsePlace *place,
 typedef struct {
 	GPtrArray *fingerprints;
 	gint current_index;
-	GOutputStream *output;
+	GString *data;
 	GCancellable *cancellable;
 	LDAP *ldap;
-} source_export_closure;
+} ExportClosure;
 
 static void
-source_export_free (gpointer data)
+export_closure_free (gpointer data)
 {
-	source_export_closure *closure = data;
+	ExportClosure *closure = data;
 	g_ptr_array_free (closure->fingerprints, TRUE);
-	g_object_unref (closure->output);
+	if (closure->data)
+		g_string_free (closure->data, TRUE);
 	g_clear_object (&closure->cancellable);
 	if (closure->ldap)
 		ldap_unbind_ext (closure->ldap, NULL, NULL);
@@ -1229,14 +1230,12 @@ on_export_search_completed (LDAPMessage *result,
                             gpointer user_data)
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	source_export_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	ExportClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
 	SeahorseLDAPSource *self = SEAHORSE_LDAP_SOURCE (g_async_result_get_source_object (G_ASYNC_RESULT (res)));
 	LDAPServerInfo *sinfo;
 	char *message;
 	GError *error = NULL;
-	gboolean ret;
 	gchar *key;
-	gsize written;
 	int code;
 	int type;
 	int rc;
@@ -1264,18 +1263,10 @@ on_export_search_completed (LDAPMessage *result,
 			return FALSE;
 		}
 
-		ret = g_output_stream_write_all (closure->output, key, strlen (key), &written, NULL, &error) &&
-		      g_output_stream_write_all (closure->output, "\n", 1, &written, NULL, &error) &&
-		      g_output_stream_flush (closure->output, NULL, &error);
+		g_string_append (closure->data, key);
+		g_string_append_c (closure->data, '\n');
 
 		g_free (key);
-
-		if (!ret) {
-			g_simple_async_result_take_error (res, error);
-			g_simple_async_result_complete (res);
-			return FALSE;
-		}
-
 		return TRUE;
 
 	/* No more entries, result */
@@ -1302,7 +1293,7 @@ static void
 export_retrieve_key (SeahorseLDAPSource *self,
                      GSimpleAsyncResult *res)
 {
-	source_export_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	ExportClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
 	LDAPServerInfo *sinfo;
 	gchar *filter;
 	char *attrs[2];
@@ -1362,7 +1353,7 @@ on_export_connect_completed (GObject *source,
                              gpointer user_data)
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	source_export_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	ExportClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
 	GError *error = NULL;
 
 	closure->ldap = seahorse_ldap_source_connect_finish (SEAHORSE_LDAP_SOURCE (source),
@@ -1380,21 +1371,20 @@ on_export_connect_completed (GObject *source,
 static void
 seahorse_ldap_source_export_async (SeahorseServerSource *source,
                                    GList *keyids,
-                                   GOutputStream *output,
                                    GCancellable *cancellable,
                                    GAsyncReadyCallback callback,
                                    gpointer user_data)
 {
 	SeahorseLDAPSource *self = SEAHORSE_LDAP_SOURCE (source);
-	source_export_closure *closure;
+	ExportClosure *closure;
 	GSimpleAsyncResult *res;
 	gchar *fingerprint;
 	GList *l;
 
 	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
 	                                 seahorse_ldap_source_export_async);
-	closure = g_new0 (source_export_closure, 1);
-	closure->output = g_object_ref (output);
+	closure = g_new0 (ExportClosure, 1);
+	closure->data = g_string_sized_new (1024);
 	closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
 	closure->fingerprints = g_ptr_array_new_with_free_func (g_free);
 	for (l = keyids; l; l = g_list_next (l)) {
@@ -1403,7 +1393,7 @@ seahorse_ldap_source_export_async (SeahorseServerSource *source,
 		seahorse_progress_prep (closure->cancellable, fingerprint, NULL);
 	}
 	closure->current_index = -1;
-	g_simple_async_result_set_op_res_gpointer (res, closure, source_export_free);
+	g_simple_async_result_set_op_res_gpointer (res, closure, export_closure_free);
 
 	seahorse_ldap_source_connect_async (self, cancellable,
 	                                    on_export_connect_completed,
@@ -1412,21 +1402,27 @@ seahorse_ldap_source_export_async (SeahorseServerSource *source,
 	g_object_unref (res);
 }
 
-static GOutputStream *
+static gpointer
 seahorse_ldap_source_export_finish (SeahorseServerSource *source,
                                     GAsyncResult *result,
+                                    gsize *size,
                                     GError **error)
 {
-	source_export_closure *closure;
+	ExportClosure *closure;
+	gpointer output;
 
+	g_return_val_if_fail (size != NULL, NULL);
 	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (source),
-	                      seahorse_ldap_source_export_async), FALSE);
+	                      seahorse_ldap_source_export_async), NULL);
 
 	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
-		return FALSE;
+		return NULL;
 
 	closure = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
-	return closure->output;
+	*size = closure->data->len;
+	output = g_string_free (closure->data, FALSE);
+	closure->data = NULL;
+	return output;
 }
 
 static void 
diff --git a/pgp/seahorse-pgp-key-properties.c b/pgp/seahorse-pgp-key-properties.c
index d29b5ef..8ee8cae 100644
--- a/pgp/seahorse-pgp-key-properties.c
+++ b/pgp/seahorse-pgp-key-properties.c
@@ -32,6 +32,8 @@
 
 #include "seahorse-bind.h"
 #include "seahorse-delete-dialog.h"
+#include "seahorse-exportable.h"
+#include "seahorse-exporter.h"
 #include "seahorse-icons.h"
 #include "seahorse-object.h"
 #include "seahorse-object-model.h"
@@ -39,6 +41,7 @@
 #include "seahorse-util.h"
 
 #include "seahorse-gpgme-dialogs.h"
+#include "seahorse-gpgme-exporter.h"
 #include "seahorse-gpgme-key.h"
 #include "seahorse-gpgme-key-op.h"
 #include "seahorse-pgp-backend.h"
@@ -1130,21 +1133,17 @@ on_pgp_details_trust_changed (GtkComboBox *selection,
 }
 
 static void
-export_complete (GFile *file, GAsyncResult *result, gchar *contents)
+on_export_complete (GObject *source,
+                    GAsyncResult *result,
+                    gpointer user_data)
 {
-	GError *err = NULL;
-	gchar *uri, *unesc_uri;
-	
-	g_free (contents);
-
-	if (!g_file_replace_contents_finish (file, result, NULL, &err)) {
-		uri = g_file_get_uri (file);
-		unesc_uri = g_uri_unescape_string (seahorse_util_uri_get_last (uri), NULL);
-		seahorse_util_handle_error (&err, NULL, _("Couldn't export key to \"%s\""),
-		                            unesc_uri);
-        g_free (uri);
-        g_free (unesc_uri);
-	}
+	GtkWindow *parent = GTK_WINDOW (user_data);
+	GError *error = NULL;
+
+	if (!seahorse_exporter_export_to_file_finish (SEAHORSE_EXPORTER (source), result, &error))
+		seahorse_util_handle_error (&error, parent, _("Couldn't export key"));
+
+	g_object_unref (parent);
 }
 
 G_MODULE_EXPORT void
@@ -1152,56 +1151,25 @@ on_pgp_details_export_button (GtkWidget *widget,
                               gpointer user_data)
 {
 	SeahorseWidget *swidget = SEAHORSE_WIDGET (user_data);
+	SeahorseExporter *exporter;
+	GList *exporters = NULL;
+	GtkWindow *window;
 	GObject *object;
-	GtkDialog *dialog;
-	gchar* uri = NULL;
-	GError *err = NULL;
 	GFile *file;
-	gpgme_error_t gerr;
-	gpgme_ctx_t ctx;
-	gpgme_data_t data;
-	gchar *results;
-	gsize n_results;
-	gpgme_key_t seckey;
 
 	object = SEAHORSE_OBJECT_WIDGET (swidget)->object;
-	g_return_if_fail (SEAHORSE_IS_GPGME_KEY (object));
 
-	seckey = seahorse_gpgme_key_get_private (SEAHORSE_GPGME_KEY (object));
-	g_return_if_fail (seckey && seckey->subkeys && seckey->subkeys->keyid);
+	exporters = g_list_append (exporters, seahorse_gpgme_exporter_new (object, TRUE, TRUE));
 
-	dialog = seahorse_util_chooser_save_new (_("Export Complete Key"), 
-	                                         GTK_WINDOW (seahorse_widget_get_toplevel (swidget)));
-	seahorse_util_chooser_show_key_files (dialog);
-	seahorse_util_chooser_set_filename (dialog, object);
-    
-	uri = seahorse_util_chooser_save_prompt (dialog);
-	if (!uri) 
-		return;
-
-	ctx = seahorse_gpgme_keyring_new_context (&gerr);
-	if (ctx == NULL)
-		return;
-
-	/* Export to a data block */
-	gerr = gpgme_data_new (&data);
-	g_return_if_fail (GPG_IS_OK (gerr));
-
-	gerr = seahorse_gpg_op_export_secret (ctx, seckey->subkeys->keyid, data);
-	gpgme_release (ctx);
-	results = gpgme_data_release_and_get_mem (data, &n_results);
-
-	if (GPG_IS_OK (gerr)) {
-		file = g_file_new_for_uri (uri);
-		g_file_replace_contents_async (file, results, n_results, NULL, FALSE, 
-		                               G_FILE_CREATE_PRIVATE, NULL, 
-		                               (GAsyncReadyCallback)export_complete, results);
-	} else {
-		seahorse_gpgme_propagate_error (gerr, &err);
-		seahorse_util_handle_error (&err, NULL, _("Couldn't export key."));
+	window = GTK_WINDOW (seahorse_widget_get_toplevel (swidget));
+	if (seahorse_exportable_prompt (exporters, window, NULL, &file, &exporter)) {
+		seahorse_exporter_export_to_file_async (exporter, file, TRUE, NULL,
+		                                        on_export_complete, g_object_ref (window));
+		g_object_unref (file);
+		g_object_unref (exporter);
 	}
-	
-	g_free (uri);
+
+	g_list_free_full (exporters, g_object_unref);
 }
 
 G_MODULE_EXPORT void
diff --git a/pgp/seahorse-server-source.c b/pgp/seahorse-server-source.c
index 39d881e..d146cab 100644
--- a/pgp/seahorse-server-source.c
+++ b/pgp/seahorse-server-source.c
@@ -399,7 +399,6 @@ seahorse_server_source_search_finish (SeahorseServerSource *self,
 void
 seahorse_server_source_export_async (SeahorseServerSource *self,
                                      GList *keyids,
-                                     GOutputStream *output,
                                      GCancellable *cancellable,
                                      GAsyncReadyCallback callback,
                                      gpointer user_data)
@@ -407,27 +406,28 @@ seahorse_server_source_export_async (SeahorseServerSource *self,
 	SeahorseServerSourceClass *klass;
 
 	g_return_if_fail (SEAHORSE_IS_SERVER_SOURCE (self));
-	g_return_if_fail (output == NULL || G_IS_OUTPUT_STREAM (output));
 	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
 	klass = SEAHORSE_SERVER_SOURCE_GET_CLASS (self);
 	g_return_if_fail (klass->export_async);
-	(klass->export_async) (self, keyids, output, cancellable, callback, user_data);
+	(klass->export_async) (self, keyids, cancellable, callback, user_data);
 }
 
-GOutputStream *
+gpointer
 seahorse_server_source_export_finish (SeahorseServerSource *self,
                                       GAsyncResult *result,
+                                      gsize *size,
                                       GError **error)
 {
 	SeahorseServerSourceClass *klass;
 
 	g_return_val_if_fail (SEAHORSE_IS_SERVER_SOURCE (self), NULL);
 	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+	g_return_val_if_fail (size != NULL, NULL);
 	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	klass = SEAHORSE_SERVER_SOURCE_GET_CLASS (self);
 	g_return_val_if_fail (klass->export_async != NULL, NULL);
 	g_return_val_if_fail (klass->export_finish != NULL, NULL);
-	return (klass->export_finish) (self, result, error);
+	return (klass->export_finish) (self, result, size, error);
 }
diff --git a/pgp/seahorse-server-source.h b/pgp/seahorse-server-source.h
index e701d03..3aab05d 100644
--- a/pgp/seahorse-server-source.h
+++ b/pgp/seahorse-server-source.h
@@ -66,13 +66,13 @@ struct _SeahorseServerSourceClass {
 
 	void            (*export_async)          (SeahorseServerSource *source,
 	                                          GList *ids,
-	                                          GOutputStream *output,
 	                                          GCancellable *cancellable,
 	                                          GAsyncReadyCallback callback,
 	                                          gpointer user_data);
 
-	GOutputStream * (*export_finish)         (SeahorseServerSource *source,
+	gpointer        (*export_finish)         (SeahorseServerSource *source,
 	                                          GAsyncResult *result,
+	                                          gsize *size,
 	                                          GError **error);
 
 	void            (*search_async)          (SeahorseServerSource *source,
@@ -104,13 +104,13 @@ gboolean               seahorse_server_source_search_finish    (SeahorseServerSo
 
 void                   seahorse_server_source_export_async     (SeahorseServerSource *self,
                                                                 GList *ids,
-                                                                GOutputStream *output,
                                                                 GCancellable *cancellable,
                                                                 GAsyncReadyCallback callback,
                                                                 gpointer user_data);
 
-GOutputStream *        seahorse_server_source_export_finish    (SeahorseServerSource *self,
+gpointer               seahorse_server_source_export_finish    (SeahorseServerSource *self,
                                                                 GAsyncResult *result,
+                                                                gsize *size,
                                                                 GError **error);
 
 #endif /* __SEAHORSE_SERVER_SOURCE_H__ */
diff --git a/pgp/seahorse-transfer.c b/pgp/seahorse-transfer.c
index 82afbd9..31494e8 100644
--- a/pgp/seahorse-transfer.c
+++ b/pgp/seahorse-transfer.c
@@ -23,35 +23,34 @@
 #include "config.h"
 
 #include "seahorse-server-source.h"
+#include "seahorse-gpgme-exporter.h"
 #include "seahorse-gpgme-keyring.h"
 
-#include <stdlib.h>
-#include <glib/gi18n.h>
-
+#define DEBUG_FLAG SEAHORSE_DEBUG_OPERATION
+#include "seahorse-debug.h"
+#include "seahorse-exporter.h"
 #include "seahorse-object-list.h"
-#include "seahorse-transfer.h"
 #include "seahorse-progress.h"
+#include "seahorse-transfer.h"
 #include "seahorse-util.h"
 
-#define DEBUG_FLAG SEAHORSE_DEBUG_OPERATION
-#include "seahorse-debug.h"
+#include <glib/gi18n.h>
+
+#include <stdlib.h>
 
 typedef struct {
 	GCancellable *cancellable;
 	SeahorsePlace *from;
 	SeahorsePlace *to;
-	GOutputStream *output;
 	GList *keys;
-} transfer_closure;
+} TransferClosure;
 
 static void
 transfer_closure_free (gpointer user_data)
 {
-	transfer_closure *closure = user_data;
-
+	TransferClosure *closure = user_data;
 	g_clear_object (&closure->from);
 	g_clear_object (&closure->to);
-	g_clear_object (&closure->output);
 	g_clear_object (&closure->cancellable);
 	seahorse_object_list_free (closure->keys);
 	g_free (closure);
@@ -63,7 +62,7 @@ on_source_import_ready (GObject *object,
                         gpointer user_data)
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	transfer_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	TransferClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
 	GError *error = NULL;
 
 	seahorse_debug ("[transfer] import done");
@@ -85,9 +84,9 @@ on_source_export_ready (GObject *object,
                         gpointer user_data)
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	transfer_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	TransferClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
 	GError *error = NULL;
-	gpointer stream_data;
+	gpointer stream_data = NULL;
 	gsize stream_size;
 	GInputStream *input;
 
@@ -95,11 +94,15 @@ on_source_export_ready (GObject *object,
 	seahorse_progress_end (closure->cancellable, &closure->from);
 
 	if (SEAHORSE_IS_SERVER_SOURCE (closure->from)) {
-		seahorse_server_source_export_finish (SEAHORSE_SERVER_SOURCE (closure->from),
-		                                      result, &error);
+		stream_data = seahorse_server_source_export_finish (SEAHORSE_SERVER_SOURCE (object),
+		                                                    result, &stream_size, &error);
+
+	} else if (SEAHORSE_IS_GPGME_KEYRING (closure->from)) {
+		stream_data = seahorse_exporter_export_finish (SEAHORSE_EXPORTER (object), result,
+		                                               &stream_size, &error);
 
 	} else {
-		seahorse_place_export_finish (closure->from, result, &error);
+		g_warning ("unsupported source for export: %s", G_OBJECT_TYPE_NAME (object));
 	}
 
 	if (error == NULL)
@@ -108,17 +111,15 @@ on_source_export_ready (GObject *object,
 	if (error == NULL) {
 		seahorse_progress_begin (closure->cancellable, &closure->to);
 
-		stream_data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (closure->output));
-		stream_size = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (closure->output));
-
 		if (!stream_size) {
 			seahorse_debug ("[transfer] nothing to import");
 			seahorse_progress_end (closure->cancellable, &closure->to);
 			g_simple_async_result_complete (res);
 
 		} else {
-			input = g_memory_input_stream_new_from_data (g_memdup (stream_data, stream_size),
-			                                             stream_size, g_free);
+			input = g_memory_input_stream_new_from_data (stream_data, stream_size, g_free);
+			stream_data = NULL;
+			stream_size = 0;
 
 			seahorse_debug ("[transfer] starting import");
 			seahorse_place_import_async (closure->to, input, closure->cancellable,
@@ -132,6 +133,7 @@ on_source_export_ready (GObject *object,
 		g_simple_async_result_complete (res);
 	}
 
+	g_free (stream_data);
 	g_object_unref (user_data);
 }
 
@@ -139,7 +141,8 @@ static gboolean
 on_timeout_start_transfer (gpointer user_data)
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	transfer_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	TransferClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
+	SeahorseExporter *exporter;
 	GList *keyids, *l;
 
 	g_assert (SEAHORSE_IS_PLACE (closure->from));
@@ -152,13 +155,18 @@ on_timeout_start_transfer (gpointer user_data)
 			keyids = g_list_prepend (keyids, (gpointer)seahorse_pgp_key_get_keyid (l->data));
 		keyids = g_list_reverse (keyids);
 		seahorse_server_source_export_async (SEAHORSE_SERVER_SOURCE (closure->from),
-		                                     keyids, closure->output, closure->cancellable,
+		                                     keyids, closure->cancellable,
 		                                     on_source_export_ready, g_object_ref (res));
 		g_list_free (keyids);
+
+	} else if (SEAHORSE_IS_GPGME_KEYRING (closure->from)) {
+		exporter = seahorse_gpgme_exporter_new_multiple (closure->keys, TRUE);
+		seahorse_exporter_export_async (exporter, closure->cancellable,
+		                                on_source_export_ready, g_object_ref (res));
+		g_object_unref (exporter);
+
 	} else {
-		seahorse_place_export_async (closure->from, closure->keys, closure->output,
-		                             closure->cancellable, on_source_export_ready,
-		                             g_object_ref (res));
+		g_warning ("unsupported source for transfer: %s", G_OBJECT_TYPE_NAME (closure->from));
 	}
 
 	return FALSE; /* Don't run again */
@@ -173,7 +181,7 @@ seahorse_transfer_async (SeahorsePlace *from,
                          gpointer user_data)
 {
 	GSimpleAsyncResult *res;
-	transfer_closure *closure = NULL;
+	TransferClosure *closure;
 
 	g_return_if_fail (SEAHORSE_PLACE (from));
 	g_return_if_fail (SEAHORSE_PLACE (to));
@@ -187,12 +195,11 @@ seahorse_transfer_async (SeahorsePlace *from,
 		return;
 	}
 
-	closure = g_new0 (transfer_closure, 1);
+	closure = g_new0 (TransferClosure, 1);
 	closure->cancellable = cancellable ? g_object_ref (cancellable) : cancellable;
 	closure->from = g_object_ref (from);
 	closure->to = g_object_ref (to);
 	closure->keys = seahorse_object_list_copy (keys);
-	closure->output = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
 	g_simple_async_result_set_op_res_gpointer (res, closure, transfer_closure_free);
 
 	seahorse_progress_prep (cancellable, &closure->from,
diff --git a/pkcs11/Makefile.am b/pkcs11/Makefile.am
index 6406266..7cdce84 100644
--- a/pkcs11/Makefile.am
+++ b/pkcs11/Makefile.am
@@ -16,6 +16,7 @@ noinst_LTLIBRARIES = libseahorse-pkcs11.la
 
 libseahorse_pkcs11_la_SOURCES = \
 	seahorse-certificate.c seahorse-certificate.h \
+	seahorse-certificate-der-exporter.c seahorse-certificate-der-exporter.h \
 	seahorse-interaction.c seahorse-interaction.h \
 	seahorse-pkcs11-actions.c seahorse-pkcs11-actions.h \
 	seahorse-pkcs11-backend.c seahorse-pkcs11-backend.h \
diff --git a/pkcs11/seahorse-certificate-der-exporter.c b/pkcs11/seahorse-certificate-der-exporter.c
new file mode 100644
index 0000000..3b6f7d6
--- /dev/null
+++ b/pkcs11/seahorse-certificate-der-exporter.c
@@ -0,0 +1,245 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU 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 "seahorse-certificate.h"
+#include "seahorse-certificate-der-exporter.h"
+
+#include "seahorse-util.h"
+
+#include <gcr/gcr.h>
+
+#include <glib/gi18n.h>
+
+#include <string.h>
+
+#define SEAHORSE_CERTIFICATE_DER_EXPORTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SEAHORSE_TYPE_CERTIFICATE_DER_EXPORTER, SeahorseCertificateDerExporterClass))
+#define SEAHORSE_IS_CERTIFICATE_DER_EXPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAHORSE_TYPE_CERTIFICATE_DER_EXPORTER))
+#define SEAHORSE_CERTIFICATE_DER_EXPORTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAHORSE_TYPE_CERTIFICATE_DER_EXPORTER, SeahorseCertificateDerExporterClass))
+
+typedef struct _SeahorseCertificateDerExporterClass SeahorseCertificateDerExporterClass;
+
+struct _SeahorseCertificateDerExporter {
+	GObject parent;
+	SeahorseCertificate *certificate;
+	GList *objects;
+};
+
+struct _SeahorseCertificateDerExporterClass {
+	GObjectClass parent_class;
+};
+
+enum {
+	PROP_0,
+	PROP_FILENAME,
+	PROP_CONTENT_TYPE,
+	PROP_FILE_FILTER,
+};
+
+static void   seahorse_certificate_der_exporter_iface_init    (SeahorseExporterIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (SeahorseCertificateDerExporter, seahorse_certificate_der_exporter, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_EXPORTER, seahorse_certificate_der_exporter_iface_init);
+);
+
+static gchar *
+calc_filename (SeahorseCertificateDerExporter *self)
+{
+	const gchar *basename = NULL;
+	gchar *label = NULL;
+	gchar *filename;
+
+	g_return_val_if_fail (self->certificate, NULL);
+
+	g_object_get (self->certificate, "label", &label, NULL);
+	if (label == NULL)
+		basename = _("Certificate");
+	else
+		basename = label;
+
+	filename = g_strconcat (basename, ".crt", NULL);
+	g_strstrip (filename);
+	g_strdelimit (filename, SEAHORSE_BAD_FILENAME_CHARS, '_');
+	g_free (label);
+	return filename;
+}
+
+static const gchar *
+calc_content_type (SeahorseCertificateDerExporter *self)
+{
+	return "application/pkix-cert";
+}
+
+static GtkFileFilter *
+calc_file_filter (SeahorseCertificateDerExporter *self)
+{
+	GtkFileFilter *filter = gtk_file_filter_new ();
+
+	gtk_file_filter_set_name (filter, _("Certificates (DER encoded)"));
+	gtk_file_filter_add_mime_type (filter, "application/pkix-cert");
+	gtk_file_filter_add_mime_type (filter, "application/x-x509-ca-cert");
+	gtk_file_filter_add_mime_type (filter, "application/x-x509-user-cert");
+	gtk_file_filter_add_pattern (filter, "*.cer");
+	gtk_file_filter_add_pattern (filter, "*.crt");
+	gtk_file_filter_add_pattern (filter, "*.cert");
+
+	return filter;
+}
+
+static void
+seahorse_certificate_der_exporter_init (SeahorseCertificateDerExporter *self)
+{
+
+}
+
+static void
+seahorse_certificate_der_exporter_get_property (GObject *object,
+                                                guint prop_id,
+                                                GValue *value,
+                                                GParamSpec *pspec)
+{
+	SeahorseCertificateDerExporter *self = SEAHORSE_CERTIFICATE_DER_EXPORTER (object);
+
+	switch (prop_id) {
+	case PROP_FILENAME:
+		g_value_take_string (value, calc_filename (self));
+		break;
+	case PROP_CONTENT_TYPE:
+		g_value_set_string (value, calc_content_type (self));
+		break;
+	case PROP_FILE_FILTER:
+		g_value_take_object (value, calc_file_filter (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+seahorse_certificate_der_exporter_finalize (GObject *obj)
+{
+	SeahorseCertificateDerExporter *self = SEAHORSE_CERTIFICATE_DER_EXPORTER (obj);
+
+	g_clear_object (&self->certificate);
+	g_list_free (self->objects);
+
+	G_OBJECT_CLASS (seahorse_certificate_der_exporter_parent_class)->finalize (obj);
+}
+
+static void
+seahorse_certificate_der_exporter_class_init (SeahorseCertificateDerExporterClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->finalize = seahorse_certificate_der_exporter_finalize;
+	gobject_class->get_property = seahorse_certificate_der_exporter_get_property;
+
+	g_object_class_override_property (gobject_class, PROP_FILENAME, "filename");
+	g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type");
+	g_object_class_override_property (gobject_class, PROP_FILE_FILTER, "file-filter");
+}
+
+static GList *
+seahorse_certificate_der_exporter_get_objects (SeahorseExporter *exporter)
+{
+	SeahorseCertificateDerExporter *self = SEAHORSE_CERTIFICATE_DER_EXPORTER (exporter);
+	return self->objects;
+}
+
+static gboolean
+seahorse_certificate_der_exporter_add_object (SeahorseExporter *exporter,
+                                              GObject *object)
+{
+	SeahorseCertificateDerExporter *self = SEAHORSE_CERTIFICATE_DER_EXPORTER (exporter);
+
+	if (self->certificate == NULL && SEAHORSE_IS_CERTIFICATE (object)) {
+		self->certificate = g_object_ref (object);
+		self->objects = g_list_append (self->objects, self->certificate);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+seahorse_certificate_der_exporter_export_async (SeahorseExporter *exporter,
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data)
+{
+	SeahorseCertificateDerExporter *self = SEAHORSE_CERTIFICATE_DER_EXPORTER (exporter);
+	GSimpleAsyncResult *res;
+
+	g_return_if_fail (self->certificate != NULL);
+
+	res = g_simple_async_result_new (G_OBJECT (exporter), callback, user_data,
+	                                 seahorse_certificate_der_exporter_export_async);
+
+	g_simple_async_result_complete_in_idle (res);
+	g_object_unref (res);
+}
+
+static gpointer
+seahorse_certificate_der_exporter_export_finish (SeahorseExporter *exporter,
+                                                 GAsyncResult *result,
+                                                 gsize *size,
+                                                 GError **error)
+{
+	SeahorseCertificateDerExporter *self = SEAHORSE_CERTIFICATE_DER_EXPORTER (exporter);
+	gconstpointer output;
+
+	g_return_val_if_fail (self->certificate != NULL, NULL);
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (exporter),
+	                      seahorse_certificate_der_exporter_export_async), NULL);
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+
+	output = gcr_certificate_get_der_data (GCR_CERTIFICATE (self->certificate), size);
+	return g_memdup (output, *size);
+}
+
+static void
+seahorse_certificate_der_exporter_iface_init (SeahorseExporterIface *iface)
+{
+	iface->add_object = seahorse_certificate_der_exporter_add_object;
+	iface->export_async = seahorse_certificate_der_exporter_export_async;
+	iface->export_finish = seahorse_certificate_der_exporter_export_finish;
+	iface->get_objects = seahorse_certificate_der_exporter_get_objects;
+}
+
+SeahorseExporter *
+seahorse_certificate_der_exporter_new (SeahorseCertificate *certificate)
+{
+	SeahorseExporter *exporter;
+
+	exporter = g_object_new (SEAHORSE_TYPE_CERTIFICATE_DER_EXPORTER,
+	                         NULL);
+
+	if (!seahorse_exporter_add_object (exporter, G_OBJECT (certificate)))
+		g_assert_not_reached ();
+
+	return exporter;
+}
diff --git a/pkcs11/seahorse-certificate-der-exporter.h b/pkcs11/seahorse-certificate-der-exporter.h
new file mode 100644
index 0000000..78d02b5
--- /dev/null
+++ b/pkcs11/seahorse-certificate-der-exporter.h
@@ -0,0 +1,41 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU 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 __SEAHORSE_CERTIFICATE_DER_EXPORTER_H__
+#define __SEAHORSE_CERTIFICATE_DER_EXPORTER_H__
+
+#include "seahorse-exporter.h"
+
+#include <glib-object.h>
+
+#define SEAHORSE_TYPE_CERTIFICATE_DER_EXPORTER            (seahorse_certificate_der_exporter_get_type ())
+#define SEAHORSE_CERTIFICATE_DER_EXPORTER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAHORSE_TYPE_CERTIFICATE_DER_EXPORTER, SeahorseCertificateDerExporter))
+#define SEAHORSE_IS_CERTIFICATE_DER_EXPORTER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAHORSE_TYPE_CERTIFICATE_DER_EXPORTER))
+
+typedef struct _SeahorseCertificateDerExporter SeahorseCertificateDerExporter;
+
+GType                     seahorse_certificate_der_exporter_get_type     (void) G_GNUC_CONST;
+
+SeahorseExporter *        seahorse_certificate_der_exporter_new          (SeahorseCertificate *certificate);
+
+#endif /* __SEAHORSE_CERTIFICATE_DER_EXPORTER_H__ */
diff --git a/pkcs11/seahorse-certificate.c b/pkcs11/seahorse-certificate.c
index e2fb500..3ae4474 100644
--- a/pkcs11/seahorse-certificate.c
+++ b/pkcs11/seahorse-certificate.c
@@ -23,6 +23,7 @@
 #include "config.h"
 
 #include "seahorse-certificate.h"
+#include "seahorse-certificate-der-exporter.h"
 #include "seahorse-pkcs11.h"
 #include "seahorse-pkcs11-actions.h"
 #include "seahorse-pkcs11-helpers.h"
@@ -30,6 +31,7 @@
 #include "seahorse-token.h"
 #include "seahorse-types.h"
 
+#include "seahorse-exportable.h"
 #include "seahorse-util.h"
 #include "seahorse-validity.h"
 
@@ -66,13 +68,17 @@ struct _SeahorseCertificatePrivate {
 	GIcon *icon;
 };
 
-static void seahorse_certificate_certificate_iface (GcrCertificateIface *iface);
-static void seahorse_certificate_object_attributes_iface (GckObjectAttributesIface *iface);
+static void   seahorse_certificate_certificate_iface           (GcrCertificateIface *iface);
+
+static void   seahorse_certificate_object_attributes_iface     (GckObjectAttributesIface *iface);
+
+static void   seahorse_certificate_exportable_iface            (SeahorseExportableIface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (SeahorseCertificate, seahorse_certificate, GCK_TYPE_OBJECT,
                          GCR_CERTIFICATE_MIXIN_IMPLEMENT_COMPARABLE ();
                          G_IMPLEMENT_INTERFACE (GCR_TYPE_CERTIFICATE, seahorse_certificate_certificate_iface);
-                         G_IMPLEMENT_INTERFACE (GCK_TYPE_OBJECT_ATTRIBUTES, seahorse_certificate_object_attributes_iface)
+                         G_IMPLEMENT_INTERFACE (GCK_TYPE_OBJECT_ATTRIBUTES, seahorse_certificate_object_attributes_iface);
+                         G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_EXPORTABLE, seahorse_certificate_exportable_iface);
 );
 
 static void
@@ -278,6 +284,21 @@ seahorse_certificate_object_attributes_iface (GckObjectAttributesIface *iface)
 	iface->n_attribute_types = G_N_ELEMENTS (REQUIRED_ATTRS);
 }
 
+static GList *
+seahorse_certificate_create_exporters (SeahorseExportable *exportable,
+                                       SeahorseExporterType type)
+{
+	SeahorseExporter *exporter;
+	exporter = seahorse_certificate_der_exporter_new (SEAHORSE_CERTIFICATE (exportable));
+	return g_list_append (NULL, exporter);
+}
+
+static void
+seahorse_certificate_exportable_iface (SeahorseExportableIface *iface)
+{
+	iface->create_exporters = seahorse_certificate_create_exporters;
+}
+
 GIcon *
 seahorse_certificate_get_icon (SeahorseCertificate *self)
 {
diff --git a/pkcs11/seahorse-pkcs11-properties.c b/pkcs11/seahorse-pkcs11-properties.c
index 409baa7..fd00890 100644
--- a/pkcs11/seahorse-pkcs11-properties.c
+++ b/pkcs11/seahorse-pkcs11-properties.c
@@ -29,6 +29,7 @@
 
 #include "seahorse-action.h"
 #include "seahorse-delete-dialog.h"
+#include "seahorse-exportable.h"
 #include "seahorse-progress.h"
 #include "seahorse-util.h"
 
@@ -155,7 +156,17 @@ static void
 on_export_certificate (GtkAction *action,
                        gpointer user_data)
 {
-	/* TODO: Exporter not done */
+	SeahorsePkcs11Properties *self = SEAHORSE_PKCS11_PROPERTIES (user_data);
+	GtkWindow *parent = GTK_WINDOW (self);
+	GError *error = NULL;
+	GList *objects;
+
+	objects = g_list_append (NULL, self->object);
+	seahorse_exportable_export_to_prompt_wait (objects, parent, &error);
+	g_list_free (objects);
+
+	if (error != NULL)
+		seahorse_util_handle_error (&error, parent, _("Failed to export certificate"));
 }
 
 static void
@@ -327,6 +338,7 @@ seahorse_pkcs11_properties_constructed (GObject *obj)
 	SeahorsePkcs11Properties *self = SEAHORSE_PKCS11_PROPERTIES (obj);
 	GObject *partner = NULL;
 	GError *error = NULL;
+	GList *exporters = NULL;
 	GtkAction *request;
 
 	G_OBJECT_CLASS (seahorse_pkcs11_properties_parent_class)->constructed (obj);
@@ -366,6 +378,14 @@ seahorse_pkcs11_properties_constructed (GObject *obj)
 		g_object_unref (partner);
 	}
 
+	if (SEAHORSE_IS_EXPORTABLE (self->object))
+		exporters = seahorse_exportable_create_exporters (SEAHORSE_EXPORTABLE (self->object),
+		                                                  SEAHORSE_EXPORTER_ANY);
+
+	request = gtk_action_group_get_action (self->actions, "export-object");
+	gtk_action_set_visible (request, exporters != NULL);
+	g_list_free_full (exporters, g_object_unref);
+
 	gtk_widget_grab_focus (GTK_WIDGET (self->viewer));
 }
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 494e867..3735718 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -16,6 +16,7 @@ gkr/seahorse-gkr-operation.c
 libegg/egg-datetime.c
 [type: gettext/glade]libseahorse/seahorse-add-keyserver.xml
 libseahorse/seahorse-context.c
+libseahorse/seahorse-exportable.c
 libseahorse/seahorse-key-manager-store.c
 libseahorse/seahorse-object.c
 libseahorse/seahorse-passphrase.c
@@ -52,6 +53,7 @@ pgp/seahorse-keyserver-sync.c
 pgp/seahorse-ldap-source.c
 pgp/seahorse-pgp-actions.c
 pgp/seahorse-pgp-backend.c
+pgp/seahorse-gpgme-exporter.c
 [type: gettext/glade]pgp/seahorse-pgp-generate.xml
 pgp/seahorse-pgp-key.c
 pgp/seahorse-pgp-key-properties.c
@@ -67,6 +69,7 @@ pgp/seahorse-transfer.c
 pgp/seahorse-unknown.c
 pgp/seahorse-unknown-source.c
 pkcs11/seahorse-certificate.c
+pkcs11/seahorse-certificate-der-exporter.c
 pkcs11/seahorse-interaction.c
 pkcs11/seahorse-pkcs11-actions.c
 pkcs11/seahorse-pkcs11-backend.c
@@ -88,6 +91,7 @@ src/seahorse-main.c
 ssh/seahorse-ssh-actions.c
 ssh/seahorse-ssh-askpass.c
 ssh/seahorse-ssh-backend.c
+ssh/seahorse-ssh-exporter.c
 ssh/seahorse-ssh-generate.c
 [type: gettext/glade]ssh/seahorse-ssh-generate.xml
 ssh/seahorse-ssh-key.c
diff --git a/src/seahorse-key-manager.ui b/src/seahorse-key-manager.ui
index ea72e35..3bbefc4 100644
--- a/src/seahorse-key-manager.ui
+++ b/src/seahorse-key-manager.ui
@@ -3,6 +3,7 @@
 		<menu name="File" action="file-menu">
 			<menuitem action="file-new"/>
 			<menuitem action="file-import"/>
+			<menuitem action="file-export"/>
 			<separator/>
 			<placeholder name="FileCommands">
 			</placeholder>
@@ -10,6 +11,7 @@
 			<menuitem action="app-quit"/>
 		</menu>
 		<menu name="Edit" action="edit-menu">
+			<menuitem action="edit-export-clipboard"/>
 			<menuitem action="edit-import-clipboard"/>
 			<menuitem action="edit-delete"/>
 			<separator/>
diff --git a/ssh/Makefile.am b/ssh/Makefile.am
index 0ff2116..af3a760 100644
--- a/ssh/Makefile.am
+++ b/ssh/Makefile.am
@@ -21,6 +21,7 @@ libseahorse_ssh_la_SOURCES = \
 	seahorse-ssh-actions.c seahorse-ssh-actions.h \
 	seahorse-ssh-backend.c seahorse-ssh-backend.h \
 	seahorse-ssh-dialogs.h \
+	seahorse-ssh-exporter.c seahorse-ssh-exporter.h \
 	seahorse-ssh-generate.c \
 	seahorse-ssh-key-data.c seahorse-ssh-key-data.h \
 	seahorse-ssh-key.c seahorse-ssh-key.h  \
diff --git a/ssh/seahorse-ssh-exporter.c b/ssh/seahorse-ssh-exporter.c
new file mode 100644
index 0000000..1f76279
--- /dev/null
+++ b/ssh/seahorse-ssh-exporter.c
@@ -0,0 +1,321 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU 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 "seahorse-ssh.h"
+#include "seahorse-ssh-key.h"
+#include "seahorse-ssh-exporter.h"
+#include "seahorse-ssh-source.h"
+
+#include "seahorse-exporter.h"
+#include "seahorse-object.h"
+#include "seahorse-util.h"
+
+#include <glib/gi18n.h>
+
+#include <string.h>
+
+#define SEAHORSE_SSH_EXPORTER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SEAHORSE_TYPE_SSH_EXPORTER, SeahorseSshExporterClass))
+#define SEAHORSE_IS_SSH_EXPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAHORSE_TYPE_SSH_EXPORTER))
+#define SEAHORSE_SSH_EXPORTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAHORSE_TYPE_SSH_EXPORTER, SeahorseSshExporterClass))
+
+typedef struct _SeahorseSshExporterClass SeahorseSshExporterClass;
+
+struct _SeahorseSshExporter {
+	GObject parent;
+	SeahorseSSHKey *key;
+	GList *objects;
+	gboolean secret;
+};
+
+struct _SeahorseSshExporterClass {
+	GObjectClass parent_class;
+};
+
+enum {
+	PROP_0,
+	PROP_FILENAME,
+	PROP_CONTENT_TYPE,
+	PROP_FILE_FILTER,
+	PROP_SECRET
+};
+
+static void   seahorse_ssh_exporter_iface_init    (SeahorseExporterIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (SeahorseSshExporter, seahorse_ssh_exporter, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_EXPORTER, seahorse_ssh_exporter_iface_init);
+);
+
+static gchar *
+calc_filename (SeahorseSshExporter *self)
+{
+	SeahorseSSHKeyData *data;
+	const gchar *location = NULL;
+	const gchar *basename = NULL;
+	gchar *filename;
+
+	g_return_val_if_fail (self->key, NULL);
+
+	data = seahorse_ssh_key_get_data (self->key);
+	if (data && !data->partial) {
+		if (self->secret && data->privfile)
+			location = data->privfile;
+		else if (!self->secret && data->pubfile)
+			location = data->pubfile;
+		if (location != NULL)
+			return g_path_get_basename (location);
+	}
+
+	basename = seahorse_object_get_nickname (SEAHORSE_OBJECT (self->key));
+	if (basename == NULL)
+		basename = _("Ssh Key");
+
+	if (self->secret) {
+		filename = g_strdup_printf ("id_%s", basename);
+		g_strstrip (filename);
+		g_strdelimit (filename, SEAHORSE_BAD_FILENAME_CHARS " ", '_');
+		return filename;
+	} else {
+		filename = g_strdup_printf ("%s.pub", basename);
+		g_strstrip (filename);
+		g_strdelimit (filename, SEAHORSE_BAD_FILENAME_CHARS, '_');
+		return filename;
+	}
+}
+
+static const gchar *
+calc_content_type (SeahorseSshExporter *self)
+{
+	if (self->secret)
+		return "application/x-pem-key";
+	else
+		return "application/x-ssh-key";
+}
+
+static GtkFileFilter *
+calc_file_filter (SeahorseSshExporter *self)
+{
+	GtkFileFilter *filter = gtk_file_filter_new ();
+
+	if (self->secret) {
+		gtk_file_filter_set_name (filter, _("Secret SSH keys"));
+		gtk_file_filter_add_mime_type (filter, "application/x-pem-key");
+		gtk_file_filter_add_pattern (filter, "id_*");
+	} else {
+		gtk_file_filter_set_name (filter, _("Public SSH keys"));
+		gtk_file_filter_add_mime_type (filter, "application/x-ssh-key");
+		gtk_file_filter_add_pattern (filter, "*.pub");
+	}
+
+	return filter;
+}
+
+static void
+seahorse_ssh_exporter_init (SeahorseSshExporter *self)
+{
+
+}
+
+static void
+seahorse_ssh_exporter_get_property (GObject *object,
+                                      guint prop_id,
+                                      GValue *value,
+                                      GParamSpec *pspec)
+{
+	SeahorseSshExporter *self = SEAHORSE_SSH_EXPORTER (object);
+
+	switch (prop_id) {
+	case PROP_FILENAME:
+		g_value_take_string (value, calc_filename (self));
+		break;
+	case PROP_CONTENT_TYPE:
+		g_value_set_string (value, calc_content_type (self));
+		break;
+	case PROP_FILE_FILTER:
+		g_value_take_object (value, calc_file_filter (self));
+		break;
+	case PROP_SECRET:
+		g_value_set_boolean (value, self->secret);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+seahorse_ssh_exporter_set_property (GObject *object,
+                                      guint prop_id,
+                                      const GValue *value,
+                                      GParamSpec *pspec)
+{
+	SeahorseSshExporter *self = SEAHORSE_SSH_EXPORTER (object);
+
+	switch (prop_id) {
+	case PROP_SECRET:
+		self->secret = g_value_get_boolean (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+seahorse_ssh_exporter_finalize (GObject *obj)
+{
+	SeahorseSshExporter *self = SEAHORSE_SSH_EXPORTER (obj);
+
+	g_clear_object (&self->key);
+
+	G_OBJECT_CLASS (seahorse_ssh_exporter_parent_class)->finalize (obj);
+}
+
+static void
+seahorse_ssh_exporter_class_init (SeahorseSshExporterClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->finalize = seahorse_ssh_exporter_finalize;
+	gobject_class->set_property = seahorse_ssh_exporter_set_property;
+	gobject_class->get_property = seahorse_ssh_exporter_get_property;
+
+	g_object_class_override_property (gobject_class, PROP_FILENAME, "filename");
+	g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type");
+	g_object_class_override_property (gobject_class, PROP_FILE_FILTER, "file-filter");
+
+	g_object_class_install_property (gobject_class, PROP_SECRET,
+	           g_param_spec_boolean ("secret", "Secret", "Secret key export",
+	                                 FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static GList *
+seahorse_ssh_exporter_get_objects (SeahorseExporter *exporter)
+{
+	SeahorseSshExporter *self = SEAHORSE_SSH_EXPORTER (exporter);
+	return self->objects;
+}
+
+static gboolean
+seahorse_ssh_exporter_add_object (SeahorseExporter *exporter,
+                                  GObject *object)
+{
+	SeahorseSshExporter *self = SEAHORSE_SSH_EXPORTER (exporter);
+	SeahorseSSHKey *key;
+	SeahorseUsage usage;
+
+	if (SEAHORSE_IS_SSH_KEY (object) && !self->key) {
+		key = SEAHORSE_SSH_KEY (object);
+		if (self->secret) {
+			usage = seahorse_object_get_usage (SEAHORSE_OBJECT (object));
+			if (usage != SEAHORSE_USAGE_PRIVATE_KEY)
+				return FALSE;
+		}
+		self->key = g_object_ref (key);
+		self->objects = g_list_append (self->objects, self->key);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+seahorse_ssh_exporter_export_async (SeahorseExporter *exporter,
+                                    GCancellable *cancellable,
+                                    GAsyncReadyCallback callback,
+                                    gpointer user_data)
+{
+	SeahorseSshExporter *self = SEAHORSE_SSH_EXPORTER (exporter);
+	GSimpleAsyncResult *res;
+
+	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+	                                 seahorse_ssh_exporter_export_async);
+
+	g_simple_async_result_complete_in_idle (res);
+	g_object_unref (res);
+ }
+
+static gpointer
+seahorse_ssh_exporter_export_finish (SeahorseExporter *exporter,
+                                     GAsyncResult *result,
+                                     gsize *size,
+                                     GError **error)
+{
+	SeahorseSshExporter *self = SEAHORSE_SSH_EXPORTER (exporter);
+	SeahorseSSHKeyData *keydata;
+	gpointer results = NULL;
+	SeahorsePlace *place;
+	gsize n_results;
+
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (exporter),
+	                      seahorse_ssh_exporter_export_async), NULL);
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+
+	keydata = seahorse_ssh_key_get_data (self->key);
+
+	if (self->secret) {
+		place = seahorse_object_get_place (SEAHORSE_OBJECT (self->key));
+		results = seahorse_ssh_source_export_private (SEAHORSE_SSH_SOURCE (place),
+		                                              self->key, &n_results, error);
+	} else {
+		if (keydata->pubfile) {
+			g_assert (keydata->rawdata);
+			results = g_strdup_printf ("%s\n", keydata->rawdata);
+			n_results = strlen (results);
+		} else {
+			g_set_error (error, SEAHORSE_ERROR, 0, "%s",
+			             _("No public key file is available for this key."));
+		}
+	}
+
+	*size = n_results;
+	return results;
+}
+
+static void
+seahorse_ssh_exporter_iface_init (SeahorseExporterIface *iface)
+{
+	iface->add_object = seahorse_ssh_exporter_add_object;
+	iface->export_async = seahorse_ssh_exporter_export_async;
+	iface->export_finish = seahorse_ssh_exporter_export_finish;
+	iface->get_objects = seahorse_ssh_exporter_get_objects;
+}
+
+SeahorseExporter *
+seahorse_ssh_exporter_new (GObject *object,
+                           gboolean secret)
+{
+	SeahorseExporter *exporter;
+
+	exporter = g_object_new (SEAHORSE_TYPE_SSH_EXPORTER,
+	                         "secret", secret,
+	                         NULL);
+
+	if (!seahorse_exporter_add_object (exporter, object))
+		g_assert_not_reached ();
+
+	return exporter;
+}
diff --git a/ssh/seahorse-ssh-exporter.h b/ssh/seahorse-ssh-exporter.h
new file mode 100644
index 0000000..d8516b5
--- /dev/null
+++ b/ssh/seahorse-ssh-exporter.h
@@ -0,0 +1,42 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ * You should have received a copy of the GNU 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 __SEAHORSE_SSH_EXPORTER_H__
+#define __SEAHORSE_SSH_EXPORTER_H__
+
+#include <glib-object.h>
+
+#include "seahorse-exporter.h"
+
+#define SEAHORSE_TYPE_SSH_EXPORTER            (seahorse_ssh_exporter_get_type ())
+#define SEAHORSE_SSH_EXPORTER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAHORSE_TYPE_SSH_EXPORTER, SeahorseSshExporter))
+#define SEAHORSE_IS_SSH_EXPORTER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAHORSE_TYPE_SSH_EXPORTER))
+
+typedef struct _SeahorseSshExporter SeahorseSshExporter;
+
+GType                     seahorse_ssh_exporter_get_type     (void) G_GNUC_CONST;
+
+SeahorseExporter *        seahorse_ssh_exporter_new          (GObject *object,
+                                                              gboolean secret);
+
+#endif /* __SEAHORSE_SSH_EXPORTER_H__ */
diff --git a/ssh/seahorse-ssh-key-properties.c b/ssh/seahorse-ssh-key-properties.c
index 902702b..376282e 100644
--- a/ssh/seahorse-ssh-key-properties.c
+++ b/ssh/seahorse-ssh-key-properties.c
@@ -21,17 +21,20 @@
  */
 #include "config.h"
 
+#include "seahorse-ssh-dialogs.h"
+#include "seahorse-ssh-exporter.h"
+#include "seahorse-ssh-key.h"
+#include "seahorse-ssh-operation.h"
+
 #include "seahorse-bind.h"
+#include "seahorse-exporter.h"
+#include "seahorse-exportable.h"
 #include "seahorse-icons.h"
 #include "seahorse-object.h"
 #include "seahorse-object-widget.h"
 #include "seahorse-util.h"
 #include "seahorse-validity.h"
 
-#include "ssh/seahorse-ssh-dialogs.h"
-#include "ssh/seahorse-ssh-key.h"
-#include "ssh/seahorse-ssh-operation.h"
-
 #include <glib/gi18n.h>
 
 #define NOTEBOOK "notebook"
@@ -188,65 +191,41 @@ on_ssh_passphrase_button_clicked (GtkWidget *widget,
 }
 
 static void
-export_complete (GFile *file, GAsyncResult *result, guchar *contents)
+on_export_complete (GObject *source,
+                    GAsyncResult *result,
+                    gpointer user_data)
 {
-	GError *err = NULL;
-	gchar *uri, *unesc_uri;
-	
-	g_free (contents);
-	
-	if (!g_file_replace_contents_finish (file, result, NULL, &err)) {
-		uri = g_file_get_uri (file);
-		unesc_uri = g_uri_unescape_string (seahorse_util_uri_get_last (uri), NULL);
-		seahorse_util_handle_error (&err, NULL, _("Couldn't export key to \"%s\""),
-		                            unesc_uri);
-        g_free (uri);
-        g_free (unesc_uri);
-	}
+	GtkWindow *parent = GTK_WINDOW (user_data);
+	GError *error = NULL;
+
+	if (!seahorse_exporter_export_to_file_finish (SEAHORSE_EXPORTER (source), result, &error))
+		seahorse_util_handle_error (&error, parent, _("Couldn't export key"));
+
+	g_object_unref (parent);
 }
 
 G_MODULE_EXPORT void
 on_ssh_export_button_clicked (GtkWidget *widget, SeahorseWidget *swidget)
 {
-	SeahorsePlace *sksrc;
+	SeahorseExporter *exporter;
+	GList *exporters = NULL;
 	GObject *object;
+	GtkWindow *window;
 	GFile *file;
-	GtkDialog *dialog;
-	guchar *results;
-	gsize n_results;
-	gchar* uri = NULL;
-	GError *err = NULL;
 
 	object = SEAHORSE_OBJECT_WIDGET (swidget)->object;
-	g_return_if_fail (SEAHORSE_IS_SSH_KEY (object));
-	g_object_get (object, "place", &sksrc, NULL);
-	g_return_if_fail (SEAHORSE_IS_SSH_SOURCE (sksrc));
 
-	dialog = seahorse_util_chooser_save_new (_("Export Complete Key"), 
-	                                         GTK_WINDOW (seahorse_widget_get_toplevel (swidget)));
-	seahorse_util_chooser_show_key_files (dialog);
-	seahorse_util_chooser_set_filename (dialog, object);
+	exporters = g_list_append (exporters, seahorse_ssh_exporter_new (object, TRUE));
 
-	uri = seahorse_util_chooser_save_prompt (dialog);
-	if (!uri) 
-		return;
-	
-	results = seahorse_ssh_source_export_private (SEAHORSE_SSH_SOURCE (sksrc), 
-	                                              SEAHORSE_SSH_KEY (object),
-	                                              &n_results, &err);
-	
-	if (results) {
-		g_return_if_fail (err == NULL);
-		file = g_file_new_for_uri (uri);
-		g_file_replace_contents_async (file, (gchar*)results, n_results, NULL, FALSE, 
-		                               G_FILE_CREATE_PRIVATE, NULL, 
-		                               (GAsyncReadyCallback)export_complete, results);
+	window = GTK_WINDOW (seahorse_widget_get_toplevel (swidget));
+	if (seahorse_exportable_prompt (exporters, window, NULL, &file, &exporter)) {
+		seahorse_exporter_export_to_file_async (exporter, file, TRUE, NULL,
+		                                        on_export_complete, g_object_ref (window));
+		g_object_unref (file);
+		g_object_unref (exporter);
 	}
-	
-	if (err)
-		seahorse_util_handle_error (&err, swidget, _("Couldn't export key."));
 
-	g_free (uri);
+	g_list_free_full (exporters, g_object_unref);
 }
 
 static void
diff --git a/ssh/seahorse-ssh-key.c b/ssh/seahorse-ssh-key.c
index 0078dff..27913d0 100644
--- a/ssh/seahorse-ssh-key.c
+++ b/ssh/seahorse-ssh-key.c
@@ -23,10 +23,16 @@
 #include "config.h"
 
 #include "seahorse-ssh-actions.h"
+#include "seahorse-ssh-exporter.h"
 #include "seahorse-ssh-key.h"
 #include "seahorse-ssh-operation.h"
 #include "seahorse-ssh-source.h"
 
+#include "seahorse-exportable.h"
+#include "seahorse-icons.h"
+#include "seahorse-place.h"
+#include "seahorse-validity.h"
+
 #include <gcr/gcr.h>
 
 #include <glib.h>
@@ -36,11 +42,6 @@
 #include <errno.h>
 #include <string.h>
 
-
-#include "seahorse-place.h"
-#include "seahorse-icons.h"
-#include "seahorse-validity.h"
-
 enum {
     PROP_0,
     PROP_KEY_DATA,
@@ -52,7 +53,11 @@ enum {
     PROP_LENGTH
 };
 
-G_DEFINE_TYPE (SeahorseSSHKey, seahorse_ssh_key, SEAHORSE_TYPE_OBJECT);
+static void       seahorse_ssh_key_exportable_iface      (SeahorseExportableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (SeahorseSSHKey, seahorse_ssh_key, SEAHORSE_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_EXPORTABLE, seahorse_ssh_key_exportable_iface);
+);
 
 /* -----------------------------------------------------------------------------
  * INTERNAL 
@@ -295,6 +300,19 @@ seahorse_ssh_key_class_init (SeahorseSSHKeyClass *klass)
                            0, G_MAXUINT, 0, G_PARAM_READABLE));
 }
 
+static GList *
+seahorse_ssh_key_create_exporters (SeahorseExportable *exportable,
+                                   SeahorseExporterType type)
+{
+	return g_list_append (NULL, seahorse_ssh_exporter_new (G_OBJECT (exportable), FALSE));
+}
+
+static void
+seahorse_ssh_key_exportable_iface (SeahorseExportableIface *iface)
+{
+	iface->create_exporters = seahorse_ssh_key_create_exporters;
+}
+
 /* -----------------------------------------------------------------------------
  * PUBLIC METHODS
  */
@@ -311,6 +329,13 @@ seahorse_ssh_key_new (SeahorsePlace *place,
     return skey;
 }
 
+SeahorseSSHKeyData *
+seahorse_ssh_key_get_data (SeahorseSSHKey *self)
+{
+	g_return_val_if_fail (SEAHORSE_IS_SSH_KEY (self), NULL);
+	return self->keydata;
+}
+
 guint 
 seahorse_ssh_key_get_algo (SeahorseSSHKey *skey)
 {
diff --git a/ssh/seahorse-ssh-key.h b/ssh/seahorse-ssh-key.h
index 46b6563..8cd9319 100644
--- a/ssh/seahorse-ssh-key.h
+++ b/ssh/seahorse-ssh-key.h
@@ -63,6 +63,8 @@ struct _SeahorseSSHKeyClass {
 SeahorseSSHKey*         seahorse_ssh_key_new                  (SeahorsePlace *sksrc,
                                                                SeahorseSSHKeyData *data);
 
+SeahorseSSHKeyData *    seahorse_ssh_key_get_data             (SeahorseSSHKey *self);
+
 void                    seahorse_ssh_key_refresh              (SeahorseSSHKey *self);
 
 GType                   seahorse_ssh_key_get_type             (void);
diff --git a/ssh/seahorse-ssh-operation.c b/ssh/seahorse-ssh-operation.c
index 25f762c..c320d7b 100644
--- a/ssh/seahorse-ssh-operation.c
+++ b/ssh/seahorse-ssh-operation.c
@@ -22,6 +22,13 @@
 
 #include "config.h"
 
+#include "seahorse-ssh-operation.h"
+
+#define DEBUG_FLAG SEAHORSE_DEBUG_OPERATION
+#include "seahorse-debug.h"
+#include "seahorse-exporter.h"
+#include "seahorse-util.h"
+
 #include <sys/wait.h>
 #include <signal.h>
 #include <sys/socket.h>
@@ -33,12 +40,6 @@
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
 
-#include "seahorse-ssh-operation.h"
-#include "seahorse-util.h"
-
-#define DEBUG_FLAG SEAHORSE_DEBUG_OPERATION
-#include "seahorse-debug.h"
-
 #define COMMAND_PASSWORD "PASSWORD "
 #define COMMAND_PASSWORD_LEN   9
 
@@ -473,26 +474,6 @@ seahorse_ssh_operation_finish (SeahorseSSHSource *source,
  * UPLOAD KEY 
  */
 
-typedef struct {
-	GMemoryOutputStream *output;
-	GCancellable *cancellable;
-	gchar *username;
-	gchar *hostname;
-	gchar *port;
-} ssh_upload_closure;
-
-static void
-ssh_upload_free (gpointer data)
-{
-	ssh_upload_closure *closure = data;
-	g_object_unref (closure->output);
-	g_clear_object (&closure->cancellable);
-	g_free (closure->username);
-	g_free (closure->hostname);
-	g_free (closure->port);
-	g_free (closure);
-}
-
 static void
 on_upload_send_complete (GObject *source,
                          GAsyncResult *result,
@@ -508,49 +489,6 @@ on_upload_send_complete (GObject *source,
 	g_object_unref (res);
 }
 
-static void
-on_upload_export_complete (GObject *source,
-                           GAsyncResult *result,
-                           gpointer user_data)
-{
-	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	ssh_upload_closure *closure = g_simple_async_result_get_op_res_gpointer (res);
-	SeahorseSshPromptInfo prompt = { _("Remote Host Password"), NULL, NULL, NULL };
-	GError *error = NULL;
-	gchar *data;
-	size_t length;
-	gchar *cmd;
-
-	if (!seahorse_place_export_finish (SEAHORSE_PLACE (source), result, &error)) {
-		g_simple_async_result_take_error (res, error);
-		g_simple_async_result_complete (res);
-		g_object_unref (res);
-		return;
-	}
-
-	/*
-	 * This command creates the .ssh directory if necessary (with appropriate permissions)
-	 * and then appends all input data onto the end of .ssh/authorized_keys
-	 */
-	/* TODO: Important, we should handle the host checking properly */
-	cmd = g_strdup_printf (SSH_PATH " '%s %s' %s %s -o StrictHostKeyChecking=no "
-	                       "\"umask 077; test -d .ssh || mkdir .ssh ; cat >> .ssh/authorized_keys\"",
-	                       closure->username, closure->hostname,
-	                       closure->port ? "-p" : "", closure->port ? closure->port : "");
-
-	if (g_output_stream_write_all (G_OUTPUT_STREAM (closure->output), "\n", 1, NULL, NULL, NULL) != 1)
-		g_return_if_reached ();
-	data = g_memory_output_stream_get_data (closure->output);
-	length = g_memory_output_stream_get_data_size (closure->output);
-
-	seahorse_ssh_operation_async (SEAHORSE_SSH_SOURCE (source), cmd, data, length,
-	                              closure->cancellable, on_upload_send_complete,
-	                              &prompt, g_object_ref (res));
-
-	g_free (cmd);
-	g_object_unref (res);
-}
-
 void
 seahorse_ssh_op_upload_async (SeahorseSSHSource *source,
                               GList *keys,
@@ -561,8 +499,12 @@ seahorse_ssh_op_upload_async (SeahorseSSHSource *source,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
 {
+	SeahorseSshPromptInfo prompt = { _("Remote Host Password"), NULL, NULL, NULL };
+	SeahorseSSHKeyData *keydata;
 	GSimpleAsyncResult *res;
-	ssh_upload_closure *closure;
+	GString *data;
+	GList *l;
+	gchar *cmd;
 
 	g_return_if_fail (keys != NULL);
 	g_return_if_fail (username && username[0]);
@@ -573,18 +515,30 @@ seahorse_ssh_op_upload_async (SeahorseSSHSource *source,
 
 	res = g_simple_async_result_new (G_OBJECT (source), callback, user_data,
 	                                 seahorse_ssh_op_upload_async);
-	closure = g_new0 (ssh_upload_closure, 1);
-	closure->output = G_MEMORY_OUTPUT_STREAM (g_memory_output_stream_new (NULL, 0, g_realloc, NULL));
-	closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-	closure->username = g_strdup (username);
-	closure->hostname = g_strdup (hostname);
-	closure->port = g_strdup (port);
-	g_simple_async_result_set_op_res_gpointer (res, closure, ssh_upload_free);
 
-	/* Buffer for what we send to the server */
-	seahorse_place_export_async (SEAHORSE_PLACE (source), keys, G_OUTPUT_STREAM (closure->output),
-	                             cancellable, on_upload_export_complete, g_object_ref (res));
+	data = g_string_sized_new (1024);
+	for (l = keys; l != NULL; l = g_list_next (l)) {
+		keydata = seahorse_ssh_key_get_data (l->data);
+		if (keydata && keydata->pubfile) {
+			g_string_append (data, keydata->rawdata);
+			g_string_append_c (data, '\n');
+		}
+	}
+
+	/*
+	 * This command creates the .ssh directory if necessary (with appropriate permissions)
+	 * and then appends all input data onto the end of .ssh/authorized_keys
+	 */
+	/* TODO: Important, we should handle the host checking properly */
+	cmd = g_strdup_printf (SSH_PATH " '%s %s' %s %s -o StrictHostKeyChecking=no "
+	                       "\"umask 077; test -d .ssh || mkdir .ssh ; cat >> .ssh/authorized_keys\"",
+	                       username, hostname, port ? "-p" : "", port ? port : "");
+
+	seahorse_ssh_operation_async (SEAHORSE_SSH_SOURCE (source), cmd, data->str, data->len,
+	                              cancellable, on_upload_send_complete,
+	                              &prompt, g_object_ref (res));
 
+	g_string_free (data, TRUE);
 	g_object_unref (res);
 
 }
diff --git a/ssh/seahorse-ssh-source.c b/ssh/seahorse-ssh-source.c
index 514e852..a30e84e 100644
--- a/ssh/seahorse-ssh-source.c
+++ b/ssh/seahorse-ssh-source.c
@@ -824,93 +824,11 @@ seahorse_ssh_source_import_finish (SeahorsePlace *place,
 	return NULL;
 }
 
-static void
-seahorse_ssh_source_export_async (SeahorsePlace *place,
-                                  GList *keys,
-                                  GOutputStream *output,
-                                  GCancellable *cancellable,
-                                  GAsyncReadyCallback callback,
-                                  gpointer user_data)
-{
-	GSimpleAsyncResult *res;
-	SeahorseSSHKeyData *keydata;
-	gchar *results = NULL;
-	gchar *raw = NULL;
-	GError *error = NULL;
-	SeahorseObject *object;
-	GList *l;
-	gsize written;
-
-	res = g_simple_async_result_new (G_OBJECT (place), callback, user_data,
-	                                 seahorse_ssh_source_export_async);
-
-	for (l = keys; l; l = g_list_next (l)) {
-		object = SEAHORSE_OBJECT (l->data);
-		g_assert (SEAHORSE_IS_SSH_KEY (object));
-
-		results = NULL;
-		raw = NULL;
-
-		keydata = NULL;
-		g_object_get (object, "key-data", &keydata, NULL);
-		g_assert (keydata);
-
-		/* We should already have the data loaded */
-		if (keydata->pubfile) {
-			g_assert (keydata->rawdata);
-			results = g_strdup_printf ("%s", keydata->rawdata);
-
-		} else if (!keydata->pubfile) {
-
-			/*
-			 * TODO: We should be able to get at this data by using ssh-keygen
-			 * to make a public key from the private
-			 */
-			g_warning ("private key without public, not exporting: %s", keydata->privfile);
-		}
-
-		/* Write the data out */
-		if (results) {
-			if (g_output_stream_write_all (output, results, strlen (results),
-			                               &written, NULL, &error))
-				g_output_stream_flush (output, NULL, &error);
-			g_free (results);
-		}
-
-		g_free (raw);
-
-		if (error != NULL) {
-			g_simple_async_result_take_error (res, error);
-			break;
-		}
-	}
-
-	g_simple_async_result_set_op_res_gpointer (res, g_object_ref (output), g_object_unref);
-	g_simple_async_result_complete_in_idle (res);
-	g_object_unref (res);
-}
-
-static GOutputStream *
-seahorse_ssh_source_export_finish (SeahorsePlace *place,
-                                   GAsyncResult *result,
-                                   GError **error)
-{
-	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (place),
-	                      seahorse_ssh_source_export_async), NULL);
-
-	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
-		return NULL;
-
-	return g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
-}
-
 static void 
 seahorse_ssh_source_place_iface (SeahorsePlaceIface *iface)
 {
 	iface->import_async = seahorse_ssh_source_import_async;
 	iface->import_finish = seahorse_ssh_source_import_finish;
-	iface->export_async = seahorse_ssh_source_export_async;
-	iface->export_finish = seahorse_ssh_source_export_finish;
 }
 
 /* -----------------------------------------------------------------------------



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]