[gthumb] started work on a generic OAuth connector



commit a2114316aee3df45dba37b0170a93a2e7bc67cc9
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Wed Apr 14 12:25:42 2010 +0200

    started work on a generic OAuth connector

 configure.ac                                       |    6 +
 extensions/Makefile.am                             |    2 +
 extensions/oauth/Makefile.am                       |   47 ++
 extensions/oauth/data/Makefile.am                  |    3 +
 extensions/oauth/data/ui/Makefile.am               |   10 +
 extensions/oauth/data/ui/oauth-account-chooser.ui  |   53 ++
 extensions/oauth/data/ui/oauth-account-manager.ui  |  103 +++
 .../oauth/data/ui/oauth-ask-authorization.ui       |   74 ++
 .../oauth/data/ui/oauth-complete-authorization.ui  |   75 ++
 extensions/oauth/main.c                            |   50 ++
 extensions/oauth/oauth-account-chooser-dialog.c    |  267 +++++++
 extensions/oauth/oauth-account-chooser-dialog.h    |   61 ++
 extensions/oauth/oauth-account-manager-dialog.c    |  250 +++++++
 extensions/oauth/oauth-account-manager-dialog.h    |   59 ++
 extensions/oauth/oauth-account.c                   |  188 +++++
 extensions/oauth/oauth-account.h                   |   69 ++
 extensions/oauth/oauth-authentication.c            |  718 +++++++++++++++++++
 extensions/oauth/oauth-authentication.h            |   73 ++
 extensions/oauth/oauth-connection.c                |  748 ++++++++++++++++++++
 extensions/oauth/oauth-connection.h                |  131 ++++
 extensions/oauth/oauth-types.h                     |   41 ++
 extensions/oauth/oauth.extension.in.in             |    6 +
 extensions/photobucket/Makefile.am                 |   57 ++
 extensions/photobucket/actions.c                   |   45 ++
 extensions/photobucket/actions.h                   |   32 +
 extensions/photobucket/callbacks.c                 |   94 +++
 extensions/photobucket/callbacks.h                 |   30 +
 extensions/photobucket/data/Makefile.am            |    3 +
 extensions/photobucket/data/ui/Makefile.am         |    8 +
 .../photobucket/data/ui/export-to-photobucket.ui   |  427 +++++++++++
 .../data/ui/photobucket-export-completed.ui        |   68 ++
 extensions/photobucket/dlg-export-to-photobucket.c |  591 ++++++++++++++++
 extensions/photobucket/dlg-export-to-photobucket.h |   31 +
 extensions/photobucket/main.c                      |   53 ++
 extensions/photobucket/photobucket-service.c       |  375 ++++++++++
 extensions/photobucket/photobucket-service.h       |   72 ++
 extensions/photobucket/photobucket.extension.in.in |   13 +
 37 files changed, 4933 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index f49edb6..8e81b35 100644
--- a/configure.ac
+++ b/configure.ac
@@ -475,9 +475,15 @@ extensions/jpeg_utils/Makefile
 extensions/list_tools/Makefile
 extensions/list_tools/data/Makefile
 extensions/list_tools/data/ui/Makefile
+extensions/oauth/Makefile
+extensions/oauth/data/Makefile
+extensions/oauth/data/ui/Makefile
 extensions/photo_importer/Makefile
 extensions/photo_importer/data/Makefile
 extensions/photo_importer/data/ui/Makefile
+extensions/photobucket/Makefile
+extensions/photobucket/data/Makefile
+extensions/photobucket/data/ui/Makefile
 extensions/picasaweb/Makefile
 extensions/picasaweb/data/Makefile
 extensions/picasaweb/data/ui/Makefile
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
index 048d8b7..7d88fb6 100644
--- a/extensions/Makefile.am
+++ b/extensions/Makefile.am
@@ -20,7 +20,9 @@ SUBDIRS = 			\
 	jpeg_utils		\
 	image_rotation		\
 	list_tools		\
+	oauth			\
 	photo_importer		\
+	photobucket		\
 	picasaweb		\
 	pixbuf_savers		\
 	raw_files		\
diff --git a/extensions/oauth/Makefile.am b/extensions/oauth/Makefile.am
new file mode 100644
index 0000000..ff9a692
--- /dev/null
+++ b/extensions/oauth/Makefile.am
@@ -0,0 +1,47 @@
+if ENABLE_WEB_SERVICES
+
+SUBDIRS = data
+
+extensiondir = $(pkglibdir)/extensions
+extension_LTLIBRARIES = liboauth.la
+
+liboauth_la_SOURCES = 				\
+	main.c					\
+	oauth-account.c				\
+	oauth-account.h				\
+	oauth-account-chooser-dialog.c		\
+	oauth-account-chooser-dialog.h		\
+	oauth-account-manager-dialog.c		\
+	oauth-account-manager-dialog.h		\
+	oauth-authentication.c			\
+	oauth-authentication.h			\
+	oauth-connection.c			\
+	oauth-connection.h			\
+	oauth-service.c				\
+	oauth-service.h				\
+	oauth-types.h				\
+	oauth-user.c				\
+	oauth-user.h
+
+liboauth_la_CFLAGS = $(GTHUMB_CFLAGS) $(LIBSOUP_CFLAGS) $(GNOME_KEYRING_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
+liboauth_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+liboauth_la_LIBADD = $(GTHUMB_LIBS) $(JPEG_LIBS) $(GNOME_KEYRING_LIBS)
+liboauth_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = oauth.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+	$(AM_V_GEN)( sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+	$< > $@ )
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files) 
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/oauth/data/Makefile.am b/extensions/oauth/data/Makefile.am
new file mode 100644
index 0000000..c1713cf
--- /dev/null
+++ b/extensions/oauth/data/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = ui
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/oauth/data/ui/Makefile.am b/extensions/oauth/data/ui/Makefile.am
new file mode 100644
index 0000000..fef8a90
--- /dev/null
+++ b/extensions/oauth/data/ui/Makefile.am
@@ -0,0 +1,10 @@
+uidir = $(pkgdatadir)/ui
+ui_DATA = 				\
+	oauth-account-chooser.ui	\
+	oauth-account-manager.ui	\
+	oauth-ask-authorization.ui	\
+	oauth-complete-authorization.ui		
+
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/oauth/data/ui/oauth-account-chooser.ui b/extensions/oauth/data/ui/oauth-account-chooser.ui
new file mode 100644
index 0000000..924669d
--- /dev/null
+++ b/extensions/oauth/data/ui/oauth-account-chooser.ui
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkVBox" id="account_chooser">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkHBox" id="hbox3">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">A_ccount:</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkComboBox" id="account_combobox">
+            <property name="width_request">300</property>
+            <property name="visible">True</property>
+            <property name="model">account_liststore</property>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="position">0</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkListStore" id="account_liststore">
+    <columns>
+      <!-- column-name account -->
+      <column type="GObject"/>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+      <!-- column-name separator -->
+      <column type="gboolean"/>
+      <!-- column-name icon_name -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+</interface>
diff --git a/extensions/oauth/data/ui/oauth-account-manager.ui b/extensions/oauth/data/ui/oauth-account-manager.ui
new file mode 100644
index 0000000..2106973
--- /dev/null
+++ b/extensions/oauth/data/ui/oauth-account-manager.ui
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkListStore" id="accounts_liststore">
+    <columns>
+      <!-- column-name data -->
+      <column type="GObject"/>
+      <!-- column-name username -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkVBox" id="account_manager">
+    <property name="visible">True</property>
+    <property name="border_width">5</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkLabel" id="label3">
+        <property name="visible">True</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">A_ccounts:</property>
+        <property name="use_underline">True</property>
+        <property name="mnemonic_widget">account_treeview</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHBox" id="hbox1">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">automatic</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkTreeView" id="account_treeview">
+                <property name="width_request">300</property>
+                <property name="height_request">150</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="model">accounts_liststore</property>
+                <property name="headers_visible">False</property>
+                <property name="headers_clickable">False</property>
+                <property name="reorderable">True</property>
+                <property name="search_column">1</property>
+                <child>
+                  <object class="GtkTreeViewColumn" id="treeviewcolumn1">
+                    <child>
+                      <object class="GtkCellRendererText" id="account_cellrenderertext"/>
+                      <attributes>
+                        <attribute name="text">1</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="vbox3">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkButton" id="delete_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <child>
+                  <object class="GtkImage" id="image3">
+                    <property name="visible">True</property>
+                    <property name="stock">gtk-delete</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/extensions/oauth/data/ui/oauth-ask-authorization.ui b/extensions/oauth/data/ui/oauth-ask-authorization.ui
new file mode 100644
index 0000000..4e9c69f
--- /dev/null
+++ b/extensions/oauth/data/ui/oauth-ask-authorization.ui
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkMessageDialog" id="ask_authorization_messagedialog">
+    <property name="border_width">5</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="type_hint">normal</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="message_type">other</property>
+    <property name="image">icon_image</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel_button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="authorize_button">
+                <property name="label" translatable="yes">_Authorize...</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="image">button_image</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">cancel_button</action-widget>
+      <action-widget response="-5">authorize_button</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkImage" id="button_image">
+    <property name="visible">True</property>
+    <property name="stock">gtk-dialog-authentication</property>
+  </object>
+  <object class="GtkImage" id="icon_image">
+    <property name="visible">True</property>
+    <property name="yalign">0</property>
+    <property name="stock">gtk-dialog-authentication</property>
+    <property name="icon-size">6</property>
+  </object>
+</interface>
diff --git a/extensions/oauth/data/ui/oauth-complete-authorization.ui b/extensions/oauth/data/ui/oauth-complete-authorization.ui
new file mode 100644
index 0000000..dc8450c
--- /dev/null
+++ b/extensions/oauth/data/ui/oauth-complete-authorization.ui
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkMessageDialog" id="complete_authorization_messagedialog">
+    <property name="border_width">5</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="type_hint">normal</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="message_type">other</property>
+    <property name="image">icon_image1</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox4">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area4">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel_button1">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+                <property name="xalign">0.50999999046325684</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="complete_button">
+                <property name="label" translatable="yes">C_ontinue</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="image">button_image1</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">cancel_button1</action-widget>
+      <action-widget response="-5">complete_button</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkImage" id="icon_image1">
+    <property name="visible">True</property>
+    <property name="yalign">0</property>
+    <property name="stock">gtk-dialog-authentication</property>
+    <property name="icon-size">6</property>
+  </object>
+  <object class="GtkImage" id="button_image1">
+    <property name="visible">True</property>
+    <property name="stock">gtk-dialog-authentication</property>
+  </object>
+</interface>
diff --git a/extensions/oauth/main.c b/extensions/oauth/main.c
new file mode 100644
index 0000000..6ae8d3c
--- /dev/null
+++ b/extensions/oauth/main.c
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+	return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/oauth/oauth-account-chooser-dialog.c b/extensions/oauth/oauth-account-chooser-dialog.c
new file mode 100644
index 0000000..ff539e4
--- /dev/null
+++ b/extensions/oauth/oauth-account-chooser-dialog.c
@@ -0,0 +1,267 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "flickr-account-chooser-dialog.h"
+
+#define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
+
+
+enum {
+	ACCOUNT_DATA_COLUMN,
+	ACCOUNT_NAME_COLUMN,
+	ACCOUNT_SEPARATOR_COLUMN,
+	ACCOUNT_ICON_COLUMN
+};
+
+
+static gpointer parent_class = NULL;
+
+
+struct _FlickrAccountChooserDialogPrivate {
+	GtkBuilder *builder;
+};
+
+
+static void
+flickr_account_chooser_dialog_finalize (GObject *object)
+{
+	FlickrAccountChooserDialog *self;
+
+	self = FLICKR_ACCOUNT_CHOOSER_DIALOG (object);
+
+	_g_object_unref (self->priv->builder);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+flickr_account_chooser_dialog_class_init (FlickrAccountChooserDialogClass *klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (FlickrAccountChooserDialogPrivate));
+
+	object_class = (GObjectClass*) klass;
+	object_class->finalize = flickr_account_chooser_dialog_finalize;
+}
+
+
+static void
+account_combobox_changed_cb (GtkComboBox *combobox,
+			     gpointer     user_data)
+{
+	FlickrAccountChooserDialog *self = user_data;
+	GtkTreeIter                 iter;
+	FlickrAccount              *account;
+
+	if (! gtk_combo_box_get_active_iter (combobox, &iter))
+		return;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (GET_WIDGET ("account_liststore")), &iter,
+			    ACCOUNT_DATA_COLUMN, &account,
+			    -1);
+
+	if (account == NULL)
+		gtk_dialog_response (GTK_DIALOG (self), FLICKR_ACCOUNT_CHOOSER_RESPONSE_NEW);
+
+	_g_object_unref (account);
+}
+
+
+static gboolean
+row_separator_func (GtkTreeModel *model,
+		    GtkTreeIter  *iter,
+		    gpointer      user_data)
+{
+	FlickrAccountChooserDialog *self = user_data;
+	gboolean                    is_separator;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (GET_WIDGET ("account_liststore")),
+			    iter,
+			    ACCOUNT_SEPARATOR_COLUMN, &is_separator,
+			    -1);
+
+	return is_separator;
+}
+
+
+static void
+flickr_account_chooser_dialog_init (FlickrAccountChooserDialog *self)
+{
+	GtkWidget *content;
+
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, FLICKR_TYPE_ACCOUNT_CHOOSER_DIALOG, FlickrAccountChooserDialogPrivate);
+	self->priv->builder = _gtk_builder_new_from_file ("flicker-account-chooser.ui", "flicker");
+
+	gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
+	gtk_dialog_set_has_separator (GTK_DIALOG (self), FALSE);
+	gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), 5);
+	gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+
+	content = _gtk_builder_get_widget (self->priv->builder, "account_chooser");
+	gtk_container_set_border_width (GTK_CONTAINER (content), 5);
+	gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), content, TRUE, TRUE, 0);
+
+	{
+		GtkCellLayout   *cell_layout;
+		GtkCellRenderer *renderer;
+
+		cell_layout = GTK_CELL_LAYOUT (GET_WIDGET ("account_combobox"));
+
+		renderer = gtk_cell_renderer_pixbuf_new ();
+		gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+		gtk_cell_layout_set_attributes (cell_layout, renderer,
+						"icon-name", ACCOUNT_ICON_COLUMN,
+						NULL);
+
+		renderer = gtk_cell_renderer_text_new ();
+		gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
+		gtk_cell_layout_set_attributes (cell_layout, renderer,
+						"text", ACCOUNT_NAME_COLUMN,
+						NULL);
+	}
+	gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (GET_WIDGET ("account_combobox")),
+					      row_separator_func,
+					      self,
+					      NULL);
+	g_signal_connect (GET_WIDGET ("account_combobox"),
+			  "changed",
+			  G_CALLBACK (account_combobox_changed_cb),
+			  self);
+
+	gtk_dialog_add_button (GTK_DIALOG (self),
+			       GTK_STOCK_NEW,
+			       FLICKR_ACCOUNT_CHOOSER_RESPONSE_NEW);
+	gtk_dialog_add_button (GTK_DIALOG (self),
+			       GTK_STOCK_CANCEL,
+			       GTK_RESPONSE_CANCEL);
+	gtk_dialog_add_button (GTK_DIALOG (self),
+			       GTK_STOCK_OK,
+			       GTK_RESPONSE_OK);
+	gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_OK);
+}
+
+
+GType
+flickr_account_chooser_dialog_get_type (void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo g_define_type_info = {
+			sizeof (FlickrAccountChooserDialogClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) flickr_account_chooser_dialog_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (FlickrAccountChooserDialog),
+			0,
+			(GInstanceInitFunc) flickr_account_chooser_dialog_init,
+			NULL
+		};
+		type = g_type_register_static (GTK_TYPE_DIALOG,
+					       "FlickrAccountChooserDialog",
+					       &g_define_type_info,
+					       0);
+	}
+
+	return type;
+}
+
+
+static void
+flickr_account_chooser_dialog_construct (FlickrAccountChooserDialog *self,
+				         GList                      *accounts,
+				         FlickrAccount              *default_account)
+{
+	GtkTreeIter  iter;
+	GList       *scan;
+	int          active = 0;
+	int          idx;
+
+	gtk_list_store_clear (GTK_LIST_STORE (GET_WIDGET ("account_liststore")));
+
+	for (scan = accounts, idx = 0; scan; scan = scan->next, idx++) {
+		FlickrAccount *account = scan->data;
+
+		if ((default_account != NULL) && (g_strcmp0 (account->username, default_account->username) == 0))
+			active = idx;
+
+		gtk_list_store_append (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter);
+		gtk_list_store_set (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter,
+				    ACCOUNT_DATA_COLUMN, account,
+				    ACCOUNT_NAME_COLUMN, account->username,
+				    ACCOUNT_SEPARATOR_COLUMN, FALSE,
+				    ACCOUNT_ICON_COLUMN, "dialog-password",
+				    -1);
+	}
+
+	gtk_list_store_append (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter);
+	gtk_list_store_set (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter,
+			    ACCOUNT_SEPARATOR_COLUMN, TRUE,
+			    -1);
+
+	gtk_list_store_append (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter);
+	gtk_list_store_set (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter,
+			    ACCOUNT_DATA_COLUMN, NULL,
+			    ACCOUNT_NAME_COLUMN, _("New authentication..."),
+			    ACCOUNT_SEPARATOR_COLUMN, FALSE,
+			    ACCOUNT_ICON_COLUMN, GTK_STOCK_NEW,
+			    -1);
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (GET_WIDGET ("account_combobox")), active);
+}
+
+
+GtkWidget *
+flickr_account_chooser_dialog_new (GList         *accounts,
+				   FlickrAccount *default_account)
+{
+	FlickrAccountChooserDialog *self;
+
+	self = g_object_new (FLICKR_TYPE_ACCOUNT_CHOOSER_DIALOG, NULL);
+	flickr_account_chooser_dialog_construct (self, accounts, default_account);
+
+	return (GtkWidget *) self;
+}
+
+
+FlickrAccount *
+flickr_account_chooser_dialog_get_active (FlickrAccountChooserDialog *self)
+{
+	GtkTreeIter    iter;
+	FlickrAccount *account;
+
+	if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (GET_WIDGET ("account_combobox")), &iter))
+		return NULL;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (GET_WIDGET ("account_liststore")), &iter,
+			    ACCOUNT_DATA_COLUMN, &account,
+			    -1);
+
+	return account;
+}
diff --git a/extensions/oauth/oauth-account-chooser-dialog.h b/extensions/oauth/oauth-account-chooser-dialog.h
new file mode 100644
index 0000000..a288527
--- /dev/null
+++ b/extensions/oauth/oauth-account-chooser-dialog.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef FLICKR_ACCOUNT_CHOOSER_DIALOG_H
+#define FLICKR_ACCOUNT_CHOOSER_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "flickr-account.h"
+
+G_BEGIN_DECLS
+
+#define FLICKR_ACCOUNT_CHOOSER_RESPONSE_NEW 1
+
+#define FLICKR_TYPE_ACCOUNT_CHOOSER_DIALOG            (flickr_account_chooser_dialog_get_type ())
+#define FLICKR_ACCOUNT_CHOOSER_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLICKR_TYPE_ACCOUNT_CHOOSER_DIALOG, FlickrAccountChooserDialog))
+#define FLICKR_ACCOUNT_CHOOSER_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), FLICKR_TYPE_ACCOUNT_CHOOSER_DIALOG, FlickrAccountChooserDialogClass))
+#define FLICKR_IS_ACCOUNT_CHOOSER_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLICKR_TYPE_ACCOUNT_CHOOSER_DIALOG))
+#define FLICKR_IS_ACCOUNT_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), FLICKR_TYPE_ACCOUNT_CHOOSER_DIALOG))
+#define FLICKR_ACCOUNT_CHOOSER_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), FLICKR_TYPE_ACCOUNT_CHOOSER_DIALOG, FlickrAccountChooserDialogClass))
+
+typedef struct _FlickrAccountChooserDialog FlickrAccountChooserDialog;
+typedef struct _FlickrAccountChooserDialogClass FlickrAccountChooserDialogClass;
+typedef struct _FlickrAccountChooserDialogPrivate FlickrAccountChooserDialogPrivate;
+
+struct _FlickrAccountChooserDialog {
+	GtkDialog parent_instance;
+	FlickrAccountChooserDialogPrivate *priv;
+};
+
+struct _FlickrAccountChooserDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType           flickr_account_chooser_dialog_get_type    (void);
+GtkWidget *     flickr_account_chooser_dialog_new         (GList                      *accounts,
+							   FlickrAccount              *default_account);
+FlickrAccount * flickr_account_chooser_dialog_get_active  (FlickrAccountChooserDialog *self);
+
+G_END_DECLS
+
+#endif /* FLICKR_ACCOUNT_CHOOSER_DIALOG_H */
diff --git a/extensions/oauth/oauth-account-manager-dialog.c b/extensions/oauth/oauth-account-manager-dialog.c
new file mode 100644
index 0000000..63e810a
--- /dev/null
+++ b/extensions/oauth/oauth-account-manager-dialog.c
@@ -0,0 +1,250 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "flickr-account.h"
+#include "flickr-account-manager-dialog.h"
+
+
+#define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
+
+
+enum {
+	ACCOUNT_DATA_COLUMN,
+	ACCOUNT_NAME_COLUMN,
+};
+
+
+static gpointer parent_class = NULL;
+
+
+struct _FlickrAccountManagerDialogPrivate {
+	GtkBuilder *builder;
+};
+
+
+static void
+flickr_account_manager_dialog_finalize (GObject *object)
+{
+	FlickrAccountManagerDialog *self;
+
+	self = FLICKR_ACCOUNT_MANAGER_DIALOG (object);
+
+	_g_object_unref (self->priv->builder);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+flickr_account_manager_dialog_class_init (FlickrAccountManagerDialogClass *klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (FlickrAccountManagerDialogPrivate));
+
+	object_class = (GObjectClass*) klass;
+	object_class->finalize = flickr_account_manager_dialog_finalize;
+}
+
+
+static void
+delete_button_clicked_cb (GtkWidget *button,
+		          gpointer   user_data)
+{
+	FlickrAccountManagerDialog *self = user_data;
+	GtkTreeModel               *tree_model;
+	GtkTreeIter                 iter;
+
+	if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (GET_WIDGET ("account_treeview"))),
+					     &tree_model,
+					     &iter))
+	{
+		gtk_list_store_remove (GTK_LIST_STORE (tree_model), &iter);
+	}
+}
+
+
+static void
+text_renderer_edited_cb (GtkCellRendererText *renderer,
+			 char                *path,
+			 char                *new_text,
+			 gpointer             user_data)
+{
+	FlickrAccountManagerDialog *self = user_data;
+	GtkTreePath                *tree_path;
+	GtkTreeIter                 iter;
+	FlickrAccount              *account;
+
+	tree_path = gtk_tree_path_new_from_string (path);
+	tree_path = gtk_tree_path_new_from_string (path);
+	if (! gtk_tree_model_get_iter (GTK_TREE_MODEL (GET_WIDGET ("accounts_liststore")),
+				       &iter,
+				       tree_path))
+	{
+		gtk_tree_path_free (tree_path);
+		return;
+	}
+	gtk_tree_path_free (tree_path);
+
+	gtk_tree_model_get (GTK_TREE_MODEL (GET_WIDGET ("accounts_liststore")), &iter,
+			    ACCOUNT_DATA_COLUMN, &account,
+			    -1);
+	flickr_account_set_username (account, new_text);
+
+	gtk_list_store_set (GTK_LIST_STORE (GET_WIDGET ("accounts_liststore")), &iter,
+			    ACCOUNT_DATA_COLUMN, account,
+			    ACCOUNT_NAME_COLUMN, new_text,
+			    -1);
+
+	g_object_unref (account);
+}
+
+
+static void
+flickr_account_manager_dialog_init (FlickrAccountManagerDialog *self)
+{
+	GtkWidget *content;
+
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, FLICKR_TYPE_ACCOUNT_MANAGER_DIALOG, FlickrAccountManagerDialogPrivate);
+	self->priv->builder = _gtk_builder_new_from_file ("flicker-account-manager.ui", "flicker");
+
+	gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
+	gtk_dialog_set_has_separator (GTK_DIALOG (self), FALSE);
+	gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), 5);
+	gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+
+	content = _gtk_builder_get_widget (self->priv->builder, "account_manager");
+	gtk_container_set_border_width (GTK_CONTAINER (content), 5);
+	gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), content, TRUE, TRUE, 0);
+
+	gtk_dialog_add_button (GTK_DIALOG (self),
+			       GTK_STOCK_NEW,
+			       FLICKR_ACCOUNT_MANAGER_RESPONSE_NEW);
+	gtk_dialog_add_button (GTK_DIALOG (self),
+			       GTK_STOCK_CANCEL,
+			       GTK_RESPONSE_CANCEL);
+	gtk_dialog_add_button (GTK_DIALOG (self),
+			       GTK_STOCK_OK,
+			       GTK_RESPONSE_OK);
+
+	g_object_set (GET_WIDGET ("account_cellrenderertext"), "editable", TRUE, NULL);
+        g_signal_connect (GET_WIDGET ("account_cellrenderertext"),
+                          "edited",
+                          G_CALLBACK (text_renderer_edited_cb),
+                          self);
+
+	g_signal_connect (GET_WIDGET ("delete_button"),
+			  "clicked",
+			  G_CALLBACK (delete_button_clicked_cb),
+			  self);
+}
+
+
+GType
+flickr_account_manager_dialog_get_type (void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo g_define_type_info = {
+			sizeof (FlickrAccountManagerDialogClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) flickr_account_manager_dialog_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (FlickrAccountManagerDialog),
+			0,
+			(GInstanceInitFunc) flickr_account_manager_dialog_init,
+			NULL
+		};
+		type = g_type_register_static (GTK_TYPE_DIALOG,
+					       "FlickrAccountManagerDialog",
+					       &g_define_type_info,
+					       0);
+	}
+
+	return type;
+}
+
+
+static void
+flickr_account_manager_dialog_construct (FlickrAccountManagerDialog *self,
+				         GList                      *accounts)
+{
+	GtkListStore *list_store;
+	GtkTreeIter   iter;
+	GList        *scan;
+
+	list_store = GTK_LIST_STORE (GET_WIDGET ("accounts_liststore"));
+	gtk_list_store_clear (list_store);
+	for (scan = accounts; scan; scan = scan->next) {
+		FlickrAccount *account = scan->data;
+
+		gtk_list_store_append (list_store, &iter);
+		gtk_list_store_set (list_store, &iter,
+				    ACCOUNT_DATA_COLUMN, account,
+				    ACCOUNT_NAME_COLUMN, account->username,
+				    -1);
+	}
+}
+
+
+GtkWidget *
+flickr_account_manager_dialog_new (GList *accounts)
+{
+	FlickrAccountManagerDialog *self;
+
+	self = g_object_new (FLICKR_TYPE_ACCOUNT_MANAGER_DIALOG, NULL);
+	flickr_account_manager_dialog_construct (self, accounts);
+
+	return (GtkWidget *) self;
+}
+
+
+GList *
+flickr_account_manager_dialog_get_accounts (FlickrAccountManagerDialog *self)
+{
+	GList        *accounts;
+	GtkTreeModel *tree_model;
+	GtkTreeIter   iter;
+
+	tree_model = (GtkTreeModel *) GET_WIDGET ("accounts_liststore");
+	if (! gtk_tree_model_get_iter_first (tree_model, &iter))
+		return NULL;
+
+	accounts = NULL;
+	do {
+		FlickrAccount *account;
+
+		gtk_tree_model_get (tree_model, &iter,
+				    ACCOUNT_DATA_COLUMN, &account,
+				    -1);
+		accounts = g_list_prepend (accounts, account);
+	}
+	while (gtk_tree_model_iter_next (tree_model, &iter));
+
+	return g_list_reverse (accounts);
+}
diff --git a/extensions/oauth/oauth-account-manager-dialog.h b/extensions/oauth/oauth-account-manager-dialog.h
new file mode 100644
index 0000000..5db8d52
--- /dev/null
+++ b/extensions/oauth/oauth-account-manager-dialog.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef FLICKR_ACCOUNT_MANAGER_DIALOG_H
+#define FLICKR_ACCOUNT_MANAGER_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define FLICKR_ACCOUNT_MANAGER_RESPONSE_NEW 1
+
+#define FLICKR_TYPE_ACCOUNT_MANAGER_DIALOG            (flickr_account_manager_dialog_get_type ())
+#define FLICKR_ACCOUNT_MANAGER_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLICKR_TYPE_ACCOUNT_MANAGER_DIALOG, FlickrAccountManagerDialog))
+#define FLICKR_ACCOUNT_MANAGER_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), FLICKR_TYPE_ACCOUNT_MANAGER_DIALOG, FlickrAccountManagerDialogClass))
+#define FLICKR_IS_ACCOUNT_MANAGER_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLICKR_TYPE_ACCOUNT_MANAGER_DIALOG))
+#define FLICKR_IS_ACCOUNT_MANAGER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), FLICKR_TYPE_ACCOUNT_MANAGER_DIALOG))
+#define FLICKR_ACCOUNT_MANAGER_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), FLICKR_TYPE_ACCOUNT_MANAGER_DIALOG, FlickrAccountManagerDialogClass))
+
+typedef struct _FlickrAccountManagerDialog FlickrAccountManagerDialog;
+typedef struct _FlickrAccountManagerDialogClass FlickrAccountManagerDialogClass;
+typedef struct _FlickrAccountManagerDialogPrivate FlickrAccountManagerDialogPrivate;
+
+struct _FlickrAccountManagerDialog {
+	GtkDialog parent_instance;
+	FlickrAccountManagerDialogPrivate *priv;
+};
+
+struct _FlickrAccountManagerDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType          flickr_account_manager_dialog_get_type     (void);
+GtkWidget *    flickr_account_manager_dialog_new          (GList                      *accounts);
+GList *        flickr_account_manager_dialog_get_accounts (FlickrAccountManagerDialog *dialog);
+
+G_END_DECLS
+
+#endif /* FLICKR_ACCOUNT_MANAGER_DIALOG_H */
diff --git a/extensions/oauth/oauth-account.c b/extensions/oauth/oauth-account.c
new file mode 100644
index 0000000..9c503c8
--- /dev/null
+++ b/extensions/oauth/oauth-account.c
@@ -0,0 +1,188 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_GNOME_KEYRING
+#include <gnome-keyring.h>
+#endif /* HAVE_GNOME_KEYRING */
+#include <gthumb.h>
+#include "oauth-account.h"
+
+
+static gpointer oauth_account_parent_class = NULL;
+
+
+static void
+oauth_account_finalize (GObject *obj)
+{
+	OAuthAccount *self;
+
+	self = OAUTH_ACCOUNT (obj);
+
+	g_free (self->username);
+	g_free (self->token);
+
+	G_OBJECT_CLASS (oauth_account_parent_class)->finalize (obj);
+}
+
+
+static void
+oauth_account_class_init (OAuthAccountClass *klass)
+{
+	oauth_account_parent_class = g_type_class_peek_parent (klass);
+	G_OBJECT_CLASS (klass)->finalize = oauth_account_finalize;
+}
+
+
+static DomElement*
+oauth_account_create_element (DomDomizable *base,
+			      DomDocument  *doc)
+{
+	OAuthAccount *self;
+	DomElement    *element;
+	gboolean       set_token;
+
+	self = OAUTH_ACCOUNT (base);
+
+	element = dom_document_create_element (doc, "account", NULL);
+	if (self->username != NULL)
+		dom_element_set_attribute (element, "username", self->username);
+
+	/* Don't save the token in the configuration file if gnome-keyring is
+	 * available. */
+
+	set_token = TRUE;
+#ifdef HAVE_GNOME_KEYRING
+	if (gnome_keyring_is_available ())
+		set_token = FALSE;
+#endif
+	if (set_token && (self->token != NULL))
+		dom_element_set_attribute (element, "token", self->token);
+
+	if (self->is_default)
+		dom_element_set_attribute (element, "default", "1");
+
+	return element;
+}
+
+
+static void
+oauth_account_load_from_element (DomDomizable *base,
+			          DomElement   *element)
+{
+	OAuthAccount *self;
+
+	self = OAUTH_ACCOUNT (base);
+
+	oauth_account_set_username (self, dom_element_get_attribute (element, "username"));
+	oauth_account_set_token (self, dom_element_get_attribute (element, "token"));
+	self->is_default = (g_strcmp0 (dom_element_get_attribute (element, "default"), "1") == 0);
+}
+
+
+static void
+oauth_account_dom_domizable_interface_init (DomDomizableIface *iface)
+{
+	iface->create_element = oauth_account_create_element;
+	iface->load_from_element = oauth_account_load_from_element;
+}
+
+
+static void
+oauth_account_instance_init (OAuthAccount *self)
+{
+}
+
+
+GType
+oauth_account_get_type (void)
+{
+	static GType oauth_account_type_id = 0;
+
+	if (oauth_account_type_id == 0) {
+		static const GTypeInfo g_define_type_info = {
+			sizeof (OAuthAccountClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) oauth_account_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (OAuthAccount),
+			0,
+			(GInstanceInitFunc) oauth_account_instance_init,
+			NULL
+		};
+		static const GInterfaceInfo dom_domizable_info = {
+			(GInterfaceInitFunc) oauth_account_dom_domizable_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		oauth_account_type_id = g_type_register_static (G_TYPE_OBJECT,
+								   "OAuthAccount",
+								   &g_define_type_info,
+								   0);
+		g_type_add_interface_static (oauth_account_type_id, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+	}
+
+	return oauth_account_type_id;
+}
+
+
+OAuthAccount *
+oauth_account_new (void)
+{
+	return g_object_new (OAUTH_TYPE_ACCOUNT, NULL);
+}
+
+
+void
+oauth_account_set_username (OAuthAccount *self,
+			    const char    *value)
+{
+	_g_strset (&self->username, value);
+}
+
+
+void
+oauth_account_set_token (OAuthAccount *self,
+			 const char    *value)
+{
+	_g_strset (&self->token, value);
+}
+
+
+int
+oauth_account_cmp (OAuthAccount *a,
+		   OAuthAccount *b)
+{
+	if ((a == NULL) && (b == NULL))
+		return 0;
+	else if (a == NULL)
+		return 1;
+	else if (b == NULL)
+		return -1;
+	else
+		return g_strcmp0 (a->username, b->username);
+}
diff --git a/extensions/oauth/oauth-account.h b/extensions/oauth/oauth-account.h
new file mode 100644
index 0000000..15f6842
--- /dev/null
+++ b/extensions/oauth/oauth-account.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef OAUTH_ACCOUNT_H
+#define OAUTH_ACCOUNT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define OAUTH_TYPE_ACCOUNT            (oauth_account_get_type ())
+#define OAUTH_ACCOUNT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), OAUTH_TYPE_ACCOUNT, OAuthAccount))
+#define OAUTH_ACCOUNT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), OAUTH_TYPE_ACCOUNT, OAuthAccountClass))
+#define OAUTH_IS_ACCOUNT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OAUTH_TYPE_ACCOUNT))
+#define OAUTH_IS_ACCOUNT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), OAUTH_TYPE_ACCOUNT))
+#define OAUTH_ACCOUNT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), OAUTH_TYPE_ACCOUNT, OAuthAccountClass))
+
+typedef struct _OAuthAccount OAuthAccount;
+typedef struct _OAuthAccountClass OAuthAccountClass;
+typedef struct _OAuthAccountPrivate OAuthAccountPrivate;
+
+struct _OAuthAccount {
+	GObject parent_instance;
+	OAuthAccountPrivate *priv;
+
+	char     *username;
+	char     *access_token;
+	char     *token_secret;
+	gboolean  is_default;
+};
+
+struct _OAuthAccountClass {
+	GObjectClass parent_class;
+};
+
+GType             oauth_account_get_type         (void);
+OAuthAccount *    oauth_account_new              (void);
+void              oauth_account_set_username     (OAuthAccount *self,
+						  const char   *value);
+void              oauth_account_set_access_token (OAuthAccount *self,
+						  const char   *value);
+void              oauth_account_set_token_secret (OAuthAccount *self,
+						  const char   *value);
+int               oauth_account_cmp              (OAuthAccount *a,
+					  	  OAuthAccount *b);
+
+G_END_DECLS
+
+#endif /* OAUTH_ACCOUNT_H */
diff --git a/extensions/oauth/oauth-authentication.c b/extensions/oauth/oauth-authentication.c
new file mode 100644
index 0000000..1101d41
--- /dev/null
+++ b/extensions/oauth/oauth-authentication.c
@@ -0,0 +1,718 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib.h>
+#ifdef HAVE_GNOME_KEYRING
+#include <gnome-keyring.h>
+#endif /* HAVE_GNOME_KEYRING */
+#include "oauth-account.h"
+#include "oauth-account-chooser-dialog.h"
+#include "oauth-account-manager-dialog.h"
+#include "oauth-authentication.h"
+
+
+#define SECRET_SEPARATOR ("::")
+#define OAUTH_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT 1
+
+
+/* Signals */
+enum {
+	READY,
+	ACCOUNTS_CHANGED,
+	LAST_SIGNAL
+};
+
+struct _OAuthAuthenticationPrivate
+{
+	OAuthConnection *conn;
+	GCancellable    *cancellable;
+	GList           *accounts;
+	OAuthAccount    *account;
+	GtkWidget       *browser;
+	GtkWidget       *dialog;
+};
+
+
+static GObjectClass *parent_class = NULL;
+static guint oauth_authentication_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+oauth_authentication_finalize (GObject *object)
+{
+	OAuthAuthentication *self;
+
+	self = OAUTH_AUTHENTICATION (object);
+	_g_object_unref (self->priv->conn);
+	_g_object_unref (self->priv->cancellable);
+	_g_object_list_unref (self->priv->accounts);
+	_g_object_unref (self->priv->account);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+oauth_authentication_class_init (OAuthAuthenticationClass *class)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (class);
+	g_type_class_add_private (class, sizeof (OAuthAuthenticationPrivate));
+
+	object_class = (GObjectClass*) class;
+	object_class->finalize = oauth_authentication_finalize;
+
+	/* signals */
+
+	oauth_authentication_signals[READY] =
+		g_signal_new ("ready",
+			      G_TYPE_FROM_CLASS (class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (OAuthAuthenticationClass, ready),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+	oauth_authentication_signals[ACCOUNTS_CHANGED] =
+		g_signal_new ("accounts_changed",
+			      G_TYPE_FROM_CLASS (class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (OAuthAuthenticationClass, accounts_changed),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+}
+
+
+static void
+oauth_authentication_init (OAuthAuthentication *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, OAUTH_TYPE_AUTHENTICATION, OAuthAuthenticationPrivate);
+	self->priv->conn = NULL;
+	self->priv->accounts = NULL;
+	self->priv->account = NULL;
+}
+
+
+GType
+oauth_authentication_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo type_info = {
+			sizeof (GthTaskClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) oauth_authentication_class_init,
+			NULL,
+			NULL,
+			sizeof (GthTask),
+			0,
+			(GInstanceInitFunc) oauth_authentication_init
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT,
+					       "OAuthAuthentication",
+					       &type_info,
+					       0);
+	}
+
+	return type;
+}
+
+
+OAuthAuthentication *
+oauth_authentication_new (OAuthConnection *conn,
+			  GCancellable    *cancellable,
+			  GtkWidget       *browser,
+			  GtkWidget       *dialog)
+{
+	OAuthAuthentication *self;
+
+	g_return_val_if_fail (conn != NULL, NULL);
+
+	self = (OAuthAuthentication *) g_object_new (OAUTH_TYPE_AUTHENTICATION, NULL);
+	self->priv->conn = g_object_ref (conn);
+	self->priv->cancellable = _g_object_ref (cancellable);
+	self->priv->accounts = oauth_accounts_load_from_file ();
+	self->priv->account = oauth_accounts_find_default (self->priv->accounts);
+	self->priv->browser = browser;
+	self->priv->dialog = dialog;
+
+	return self;
+}
+
+
+static void show_choose_account_dialog (OAuthAuthentication *self);
+
+
+static void
+authentication_error_dialog_response_cb (GtkDialog *dialog,
+					 int        response_id,
+					 gpointer   user_data)
+{
+	OAuthAuthentication *self = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gtk_widget_destroy (self->priv->dialog);
+		break;
+
+	case OAUTH_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		show_choose_account_dialog (self);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void start_authorization_process (OAuthAuthentication *self);
+
+
+static void
+show_authentication_error_dialog (OAuthAuthentication  *self,
+				  GError              **error)
+{
+	GtkWidget *dialog;
+
+	if (g_error_matches (*error, OAUTH_CONNECTION_ERROR, OAUTH_CONNECTION_ERROR_INVALID_TOKEN)) {
+		start_authorization_process (self);
+		return;
+	}
+
+	if (self->priv->conn != NULL)
+		gth_task_dialog (GTH_TASK (self->priv->conn), TRUE);
+
+	dialog = _gtk_message_dialog_new (GTK_WINDOW (self->priv->browser),
+					  GTK_DIALOG_MODAL,
+					  GTK_STOCK_DIALOG_ERROR,
+					  _("Could not connect to the server"),
+					  (*error)->message,
+					  _("Choose _Account..."), OAUTH_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT,
+					  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					  NULL);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (authentication_error_dialog_response_cb),
+			  self);
+	gtk_widget_show (dialog);
+
+	g_clear_error (error);
+}
+
+
+/* -- oauth_authentication_auto_connect -- */
+
+
+static void
+upload_status_ready_cb (GObject      *source_object,
+			GAsyncResult *res,
+			gpointer      user_data)
+{
+	OAuthAuthentication *self = user_data;
+	GError              *error = NULL;
+
+	if (! oauth_service_get_upload_status_finish (OAUTH_SERVICE (source_object), res, &error)) {
+		show_authentication_error_dialog (self, &error);
+		return;
+	}
+	oauth_accounts_save_to_file (self->priv->accounts, self->priv->account);
+	g_signal_emit (self, oauth_authentication_signals[READY], 0);
+}
+
+
+static void
+connect_to_server_step2 (OAuthAuthentication *self)
+{
+	if (self->priv->account->access_token == NULL) {
+		start_authorization_process (self);
+		return;
+	}
+	oauth_connection_set_token (self->priv->conn, self->priv->account->access_token, self->priv->account->token_secret);
+
+	/* FIXME: oauth_connection_get_user_info */
+	oauth_service_get_upload_status (self->priv->service,
+					 self->priv->cancellable,
+					 upload_status_ready_cb,
+					 self);
+}
+
+
+#ifdef HAVE_GNOME_KEYRING
+static void
+find_password_cb (GnomeKeyringResult  result,
+                  const char         *string,
+                  gpointer            user_data)
+{
+	OAuthAuthentication *self = user_data;
+
+	if (string != NULL) {
+		char **values;
+
+		values = g_strsplit (string, SECRET_SEPARATOR, 2);
+		if ((values[0] != NULL) && (values[1] != NULL)) {
+			self->priv->account->access_token = g_strdup (values[0]);
+			self->priv->account->token_secret = g_strdup (values[1]);
+		}
+
+		g_strfreev (values);
+	}
+
+	connect_to_server_step2 (self);
+}
+#endif
+
+
+static void
+connect_to_server (OAuthAuthentication *self)
+{
+	g_return_if_fail (self->priv->account != NULL);
+
+#ifdef HAVE_GNOME_KEYRING
+	if (((self->priv->account->access_token == NULL) || (self->priv->account->token_secret == NULL)) && gnome_keyring_is_available ()) {
+		gnome_keyring_find_password (GNOME_KEYRING_NETWORK_PASSWORD,
+					     find_password_cb,
+					     self,
+					     NULL,
+					     "user", self->priv->account->username,
+					     "server", self->priv->conn->consumer->url,
+					     "protocol", self->priv->conn->consumer->protocol,
+					     NULL);
+		return;
+	}
+#endif
+
+	connect_to_server_step2 (self);
+}
+
+
+static void
+set_account (OAuthAuthentication *self,
+	     OAuthAccount        *account)
+{
+	GList *link;
+
+	link = g_list_find_custom (self->priv->accounts, self->priv->account, (GCompareFunc) oauth_account_cmp);
+	if (link != NULL) {
+		self->priv->accounts = g_list_remove_link (self->priv->accounts, link);
+		_g_object_list_unref (link);
+	}
+
+	_g_object_unref (self->priv->account);
+	self->priv->account = NULL;
+
+	if (account != NULL) {
+		self->priv->account = g_object_ref (account);
+		self->priv->accounts = g_list_prepend (self->priv->accounts, g_object_ref (self->priv->account));
+	}
+}
+
+
+#ifdef HAVE_GNOME_KEYRING
+static void
+store_password_done_cb (GnomeKeyringResult result,
+			gpointer           user_data)
+{
+	OAuthAuthentication *self = user_data;
+	connect_to_server (self);
+}
+#endif
+
+
+static void
+get_access_token_ready_cb (GObject      *source_object,
+			   GAsyncResult *res,
+			   gpointer      user_data)
+{
+	OAuthAuthentication *self = user_data;
+	GError              *error = NULL;
+	OAuthAccount        *account;
+
+	if (! oauth_connection_get_access_token_finish (OAUTH_CONNECTION (source_object), res, &error)) {
+		show_authentication_error_dialog (self, &error);
+		return;
+	}
+
+	account = oauth_account_new ();
+	oauth_account_set_username (account, oauth_connection_get_username (self->priv->conn));
+	oauth_account_set_access_token (account, oauth_connection_get_token (self->priv->conn));
+	oauth_account_set_token_secret (account, oauth_connection_get_token_secret (self->priv->conn));
+	set_account (self, account);
+
+#ifdef HAVE_GNOME_KEYRING
+	if (gnome_keyring_is_available ()) {
+		char *secret;
+
+		secret = g_strconcat (account->access_token,
+				      TOKEN_SECRET_SEPARATOR,
+				      account->token_secret,
+				      NULL);
+		gnome_keyring_store_password (GNOME_KEYRING_NETWORK_PASSWORD,
+					      NULL,
+					      self->priv->conn->consumer->name,
+					      secret,
+					      store_password_done_cb,
+					      self,
+					      NULL,
+					      "user", account->username,
+					      "server", self->priv->conn->consumer->url,
+					      "protocol", self->priv->conn->consumer->protocol,
+					      NULL);
+		return;
+	}
+#endif
+
+	g_object_unref (account);
+	connect_to_server (self);
+}
+
+
+static void
+complete_authorization_messagedialog_response_cb (GtkDialog *dialog,
+						  int        response_id,
+						  gpointer   user_data)
+{
+	OAuthAuthentication *self = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_HELP:
+		show_help_dialog (GTK_WINDOW (dialog), "oauth-complete-authorization");
+		break;
+
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gtk_widget_destroy (self->priv->dialog);
+		break;
+
+	case GTK_RESPONSE_OK:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gth_task_dialog (GTH_TASK (self->priv->conn), FALSE);
+		oauth_connection_get_access_token (self->priv->conn,
+						   self->priv->cancellable,
+						   get_access_token_ready_cb,
+						   self);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+complete_authorization (OAuthAuthentication *self)
+{
+	GtkBuilder *builder;
+	GtkWidget  *dialog;
+	char       *text;
+	char       *secondary_text;
+
+	gth_task_dialog (GTH_TASK (self->priv->conn), TRUE);
+
+	builder = _gtk_builder_new_from_file ("oauth-complete-authorization.ui", "oauth");
+	dialog = _gtk_builder_get_widget (builder, "complete_authorization_messagedialog");
+	text = g_strdup_printf (_("Return to this window when you have finished the authorization process on %s"), self->priv->conn->consumer->name);
+	secondary_text = g_strdup (_("Once you're done, click the 'Continue' button below."));
+	g_object_set (dialog, "text", text, "secondary-text", secondary_text, NULL);
+	g_object_set_data_full (G_OBJECT (dialog), "builder", builder, g_object_unref);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (complete_authorization_messagedialog_response_cb),
+			  self);
+
+	if (GTK_WIDGET_VISIBLE (self->priv->dialog))
+		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->dialog));
+	else
+		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->browser));
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_present (GTK_WINDOW (dialog));
+
+	g_free (secondary_text);
+	g_free (text);
+}
+
+
+static void
+ask_authorization_messagedialog_response_cb (GtkDialog *dialog,
+					     int        response_id,
+					     gpointer   user_data)
+{
+	OAuthAuthentication *self = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_HELP:
+		show_help_dialog (GTK_WINDOW (dialog), "flicker-ask-authorization");
+		break;
+
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gtk_widget_destroy (self->priv->dialog);
+		break;
+
+	case GTK_RESPONSE_OK:
+		{
+			char   *url;
+			GError *error = NULL;
+
+			gtk_widget_destroy (GTK_WIDGET (dialog));
+
+			url = oauth_connection_get_login_link (self->priv->conn);
+			if (gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (dialog)), url, 0, &error))
+				complete_authorization (self);
+			else
+				show_authentication_error_dialog (self, &error);
+
+			g_free (url);
+		}
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+ask_authorization (OAuthAuthentication *self)
+{
+	GtkBuilder *builder;
+	GtkWidget  *dialog;
+	char       *text;
+	char       *secondary_text;
+
+	gth_task_dialog (GTH_TASK (self->priv->conn), TRUE);
+
+	builder = _gtk_builder_new_from_file ("oauth-ask-authorization.ui", "oauth");
+	dialog = _gtk_builder_get_widget (builder, "ask_authorization_messagedialog");
+	text = g_strdup_printf (_("gthumb requires your authorization to upload the photos to %s"), self->priv->conn->consumer->name);
+	secondary_text = g_strdup_printf (_("Click 'Authorize' to open your web browser and authorize gthumb to upload photos to %s. When you're finished, return to this window to complete the authorization."), self->priv->conn->consumer->name);
+	g_object_set (dialog, "text", text, "secondary-text", secondary_text, NULL);
+	g_object_set_data_full (G_OBJECT (dialog), "builder", builder, g_object_unref);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (ask_authorization_messagedialog_response_cb),
+			  self);
+
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	if (GTK_WIDGET_VISIBLE (self->priv->dialog))
+		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->dialog));
+	else
+		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->browser));
+	gtk_window_present (GTK_WINDOW (dialog));
+
+	g_free (secondary_text);
+	g_free (text);
+}
+
+
+static void
+connection_frob_ready_cb (GObject      *source_object,
+			  GAsyncResult *res,
+			  gpointer      user_data)
+{
+	OAuthAuthentication *self = user_data;
+	GError              *error = NULL;
+
+	if (! oauth_connection_login_request_finish (OAUTH_CONNECTION (source_object), res, &error))
+		show_authentication_error_dialog (self, &error);
+	else
+		ask_authorization (self);
+}
+
+
+static void
+start_authorization_process (OAuthAuthentication *self)
+{
+	oauth_connection_login_request (self->priv->conn,
+					self->priv->cancellable,
+					login_request_ready_cb,
+					self);
+}
+
+
+static void
+account_chooser_dialog_response_cb (GtkDialog *dialog,
+				    int        response_id,
+				    gpointer   user_data)
+{
+	OAuthAuthentication *self = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gtk_widget_destroy (self->priv->dialog);
+		break;
+
+	case GTK_RESPONSE_OK:
+		_g_object_unref (self->priv->account);
+		self->priv->account = oauth_account_chooser_dialog_get_active (OAUTH_ACCOUNT_CHOOSER_DIALOG (dialog));
+		if (self->priv->account != NULL) {
+			gtk_widget_destroy (GTK_WIDGET (dialog));
+			connect_to_server (self);
+		}
+		break;
+
+	case OAUTH_ACCOUNT_CHOOSER_RESPONSE_NEW:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		start_authorization_process (self);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+show_choose_account_dialog (OAuthAuthentication *self)
+{
+	GtkWidget *dialog;
+
+	gth_task_dialog (GTH_TASK (self->priv->conn), TRUE);
+	dialog = oauth_account_chooser_dialog_new (self->priv->accounts, self->priv->account);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (account_chooser_dialog_response_cb),
+			  self);
+
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Choose Account"));
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->browser));
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_present (GTK_WINDOW (dialog));
+}
+
+
+void
+oauth_authentication_auto_connect (OAuthAuthentication *self)
+{
+	gtk_widget_hide (self->priv->dialog);
+	gth_task_dialog (GTH_TASK (self->priv->conn), FALSE);
+
+	if (self->priv->accounts != NULL) {
+		if (self->priv->account != NULL) {
+			connect_to_server (self);
+		}
+		else if (self->priv->accounts->next == NULL) {
+			self->priv->account = g_object_ref (self->priv->accounts->data);
+			connect_to_server (self);
+		}
+		else
+			show_choose_account_dialog (self);
+	}
+	else
+		start_authorization_process (self);
+}
+
+
+void
+oauth_authentication_connect (OAuthAuthentication *self,
+			      OAuthAccount        *account)
+{
+	set_account (self, account);
+	oauth_authentication_auto_connect (self);
+}
+
+
+OAuthAccount *
+oauth_authentication_get_account (OAuthAuthentication *self)
+{
+	return self->priv->account;
+}
+
+
+GList *
+oauth_authentication_get_accounts (OAuthAuthentication *self)
+{
+	return self->priv->accounts;
+}
+
+
+/* -- oauth_authentication_edit_accounts -- */
+
+
+static void
+account_manager_dialog_response_cb (GtkDialog *dialog,
+			            int        response_id,
+			            gpointer   user_data)
+{
+	OAuthAuthentication *self = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+
+	case GTK_RESPONSE_OK:
+		_g_object_list_unref (self->priv->accounts);
+		self->priv->accounts = oauth_account_manager_dialog_get_accounts (OAUTH_ACCOUNT_MANAGER_DIALOG (dialog));
+		if (! g_list_find_custom (self->priv->accounts, self->priv->account, (GCompareFunc) oauth_account_cmp)) {
+			_g_object_unref (self->priv->account);
+			self->priv->account = NULL;
+			oauth_authentication_auto_connect (self);
+		}
+		else
+			g_signal_emit (self, oauth_authentication_signals[ACCOUNTS_CHANGED], 0);
+		oauth_accounts_save_to_file (self->priv->accounts, self->priv->account);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+
+	case OAUTH_ACCOUNT_MANAGER_RESPONSE_NEW:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		start_authorization_process (self);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+void
+oauth_authentication_edit_accounts (OAuthAuthentication *self,
+				     GtkWindow            *parent)
+{
+	GtkWidget  *dialog;
+
+	dialog = oauth_account_manager_dialog_new (self->priv->accounts);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (account_manager_dialog_response_cb),
+			  self);
+
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Edit Accounts"));
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->dialog));
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_present (GTK_WINDOW (dialog));
+}
diff --git a/extensions/oauth/oauth-authentication.h b/extensions/oauth/oauth-authentication.h
new file mode 100644
index 0000000..54097a8
--- /dev/null
+++ b/extensions/oauth/oauth-authentication.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef OAUTH_AUTHENTICATION_H
+#define OAUTH_AUTHENTICATION_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include "oauth-account.h"
+#include "oauth-connection.h"
+
+G_BEGIN_DECLS
+
+#define OAUTH_TYPE_AUTHENTICATION            (oauth_authentication_get_type ())
+#define OAUTH_AUTHENTICATION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), OAUTH_TYPE_AUTHENTICATION, OAuthAuthentication))
+#define OAUTH_AUTHENTICATION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), OAUTH_TYPE_AUTHENTICATION, OAuthAuthenticationClass))
+#define OAUTH_IS_AUTHENTICATION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OAUTH_TYPE_AUTHENTICATION))
+#define OAUTH_IS_AUTHENTICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), OAUTH_TYPE_AUTHENTICATION))
+#define OAUTH_AUTHENTICATION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), OAUTH_TYPE_AUTHENTICATION, OAuthAuthenticationClass))
+
+typedef struct _OAuthAuthentication OAuthAuthentication;
+typedef struct _OAuthAuthenticationClass OAuthAuthenticationClass;
+typedef struct _OAuthAuthenticationPrivate OAuthAuthenticationPrivate;
+
+struct _OAuthAuthentication {
+	GObject parent_instance;
+	OAuthAuthenticationPrivate *priv;
+};
+
+struct _OAuthAuthenticationClass {
+	GObjectClass parent_class;
+
+	/*< signals >*/
+
+	void  (*ready)             (OAuthAuthentication *auth);
+	void  (*accounts_changed)  (OAuthAuthentication *auth);
+};
+
+GType                   oauth_authentication_get_type       (void);
+OAuthAuthentication *   oauth_authentication_new            (OAuthConnection     *conn,
+							     GCancellable        *cancellable,
+							     GtkWidget           *browser,
+							     GtkWidget           *dialog);
+void                    oauth_authentication_auto_connect   (OAuthAuthentication *auth);
+void                    oauth_authentication_connect        (OAuthAuthentication *auth,
+							     OAuthAccount        *account);
+OAuthAccount *          oauth_authentication_get_account    (OAuthAuthentication *auth);
+GList *                 oauth_authentication_get_accounts   (OAuthAuthentication *auth);
+void                    oauth_authentication_edit_accounts  (OAuthAuthentication *auth,
+							     GtkWindow           *parent);
+
+G_END_DECLS
+
+#endif /* OAUTH_AUTHENTICATION_H */
diff --git a/extensions/oauth/oauth-connection.c b/extensions/oauth/oauth-connection.c
new file mode 100644
index 0000000..f1053af
--- /dev/null
+++ b/extensions/oauth/oauth-connection.c
@@ -0,0 +1,748 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gthumb.h>
+#include "oauth-connection.h"
+
+
+#define DEBUG_OAUTH_CONNECTION 1
+#define OAUTH_VERSION "1.0"
+#define OAUTH_SIGNATURE_METHOD "HMAC-SHA1"
+
+
+GQuark
+oauth_connection_error_quark (void)
+{
+	static GQuark quark;
+
+        if (!quark)
+                quark = g_quark_from_static_string ("oauth_connection");
+
+        return quark;
+}
+
+
+/* -- OAuthConnection -- */
+
+
+struct _OAuthConnectionPrivate
+{
+	SoupSession        *session;
+	char               *timestamp;
+	char               *nonce;
+	char               *signature;
+	char               *token;
+	GCancellable       *cancellable;
+	GSimpleAsyncResult *result;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+oauth_connection_finalize (GObject *object)
+{
+	OAuthConnection *self;
+
+	self = OAUTH_CONNECTION (object);
+
+	_g_object_unref (self->priv->result);
+	_g_object_unref (self->priv->cancellable);
+	g_free (self->priv->token);
+	g_free (self->priv->signature);
+	g_free (self->priv->nonce);
+	g_free (self->priv->timestamp);
+	_g_object_unref (self->priv->session);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+oauth_connection_exec (GthTask *base)
+{
+	/* void */
+}
+
+
+static void
+oauth_connection_cancelled (GthTask *base)
+{
+	/* void */
+}
+
+
+static void
+oauth_connection_class_init (OAuthConnectionClass *klass)
+{
+	GObjectClass *object_class;
+	GthTaskClass *task_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (OAuthConnectionPrivate));
+
+	object_class = (GObjectClass*) klass;
+	object_class->finalize = oauth_connection_finalize;
+
+	task_class = (GthTaskClass*) klass;
+	task_class->exec = oauth_connection_exec;
+	task_class->cancelled = oauth_connection_cancelled;
+}
+
+
+static void
+oauth_connection_init (OAuthConnection *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, OAUTH_TYPE_CONNECTION, OAuthConnectionPrivate);
+	self->priv->session = NULL;
+	self->priv->timestamp = NULL;
+	self->priv->nonce = NULL;
+	self->priv->signature = NULL;
+	self->priv->token = NULL;
+	self->priv->cancellable = NULL;
+	self->priv->result = NULL;
+}
+
+
+GType
+oauth_connection_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo type_info = {
+			sizeof (OAuthConnectionClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) oauth_connection_class_init,
+			NULL,
+			NULL,
+			sizeof (OAuthConnection),
+			0,
+			(GInstanceInitFunc) oauth_connection_init
+		};
+
+		type = g_type_register_static (GTH_TYPE_TASK,
+					       "OAuthConnection",
+					       &type_info,
+					       0);
+	}
+
+	return type;
+}
+
+
+OAuthConnection *
+oauth_connection_new (OAuthServer *server)
+{
+	OAuthConnection *self;
+
+	self = (OAuthConnection *) g_object_new (OAUTH_TYPE_CONNECTION, NULL);
+	self->consumer = server;
+
+	return self;
+}
+
+
+void
+oauth_connection_send_message (OAuthConnection     *self,
+			       SoupMessage         *msg,
+			       GCancellable        *cancellable,
+			       GAsyncReadyCallback  callback,
+			       gpointer             user_data,
+			       gpointer             source_tag,
+			       SoupSessionCallback  soup_session_cb,
+			       gpointer             soup_session_cb_data)
+{
+	if (self->priv->session == NULL) {
+		self->priv->session = soup_session_async_new_with_options (
+#ifdef HAVE_LIBSOUP_GNOME
+			SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_GNOME,
+#endif
+			NULL);
+
+#ifdef DEBUG_OAUTH_CONNECTION
+		{
+			SoupLogger *logger;
+
+			logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
+			soup_session_add_feature (self->priv->session, SOUP_SESSION_FEATURE (logger));
+
+			g_object_unref (logger);
+		}
+#endif
+	}
+
+	_g_object_unref (self->priv->cancellable);
+	self->priv->cancellable = _g_object_ref (cancellable);
+
+	_g_object_unref (self->priv->result);
+	self->priv->result = g_simple_async_result_new (G_OBJECT (soup_session_cb_data),
+							callback,
+							user_data,
+							source_tag);
+
+	soup_session_queue_message (self->priv->session,
+				    msg,
+				    soup_session_cb,
+				    soup_session_cb_data);
+}
+
+
+GSimpleAsyncResult *
+oauth_connection_get_result (OAuthConnection *self)
+{
+	return self->priv->result;
+}
+
+
+void
+oauth_connection_reset_result (OAuthConnection *self)
+{
+	_g_object_unref (self->priv->result);
+	self->priv->result = NULL;
+}
+
+
+/* -- oauth_connection_add_signature -- */
+
+
+static char *
+oauth_create_timestamp (GTimeVal *t)
+{
+	return g_strdup_printf ("%ld", t->tv_sec);
+}
+
+
+static char *
+oauth_create_nonce (GTimeVal *t)
+{
+	char *s;
+	char *v;
+
+	s = g_strdup_printf ("%ld%u", t->tv_usec, g_random_int ());
+	v = g_compute_checksum_for_string (G_CHECKSUM_MD5, s, -1);
+
+	g_free (s);
+
+	return v;
+}
+
+
+void
+oauth_connection_add_signature (OAuthConnection *self,
+				const char      *method,
+				const char      *url,
+			        GHashTable      *parameters)
+{
+	GTimeVal  t;
+	GString  *param_string;
+	GList    *keys;
+	GList    *scan;
+	GString  *base_string;
+	GString  *signature_key;
+	char     *signature;
+
+	/* Add the OAuth specific parameters */
+
+	g_get_current_time (&t);
+
+	g_free (self->priv->timestamp);
+	self->priv->timestamp = oauth_create_timestamp (&t);
+	g_hash_table_insert (parameters, "oauth_timestamp", self->priv->timestamp);
+
+	g_free (self->priv->nonce);
+	self->priv->nonce = oauth_create_nonce (&t);
+	g_hash_table_insert (parameters, "oauth_nonce", self->priv->nonce);
+
+	g_hash_table_insert (parameters, "format", "xml");
+	g_hash_table_insert (parameters, "oauth_version", OAUTH_VERSION);
+	g_hash_table_insert (parameters, "oauth_signature_method", OAUTH_SIGNATURE_METHOD);
+	g_hash_table_insert (parameters, "oauth_consumer_key", (gpointer) self->consumer->consumer_key);
+	if (self->priv->token != NULL)
+		g_hash_table_insert (parameters, "oauth_token", self->priv->token);
+
+	/* Create parameter string */
+
+	param_string = g_string_new ("");
+	keys = g_hash_table_get_keys (parameters);
+	keys = g_list_sort (keys, (GCompareFunc) strcmp);
+	for (scan = keys; scan; scan = scan->next) {
+		char *key = scan->data;
+
+		g_string_append (param_string, key);
+		g_string_append (param_string, "=");
+		g_string_append_uri_escaped (param_string,
+					     g_hash_table_lookup (parameters, key),
+					     NULL,
+					     TRUE);
+		if (scan->next != NULL)
+			g_string_append (param_string, "&");
+	}
+
+	/* Create the Base String */
+
+	base_string = g_string_new ("");
+	g_string_append_uri_escaped (base_string, method, NULL, TRUE);
+	g_string_append (base_string, "&");
+	g_string_append_uri_escaped (base_string, url, NULL, TRUE);
+	g_string_append (base_string, "&");
+	g_string_append_uri_escaped (base_string, param_string->str, NULL, TRUE);
+
+	/* Calculate the signature value */
+
+	signature_key = g_string_new ("");
+	g_string_append (signature_key, self->consumer->consumer_key);
+	g_string_append (signature_key, "&");
+	if (self->priv->token != NULL)
+		g_string_append (signature_key, self->priv->token);
+	g_free (self->priv->signature);
+	self->priv->signature = g_compute_signature_for_string (G_CHECKSUM_SHA1,
+							        signature_key,
+							        base_string->str,
+							        base_string->len);
+	g_hash_table_insert (parameters, "oauth_signature", self->priv->signature);
+
+	g_string_free (signature_key, TRUE);
+	g_string_free (base_string, TRUE);
+	g_list_free (keys);
+	g_string_free (param_string, TRUE);
+}
+
+
+/* -- oauth_connection_login_request -- */
+
+
+static void
+login_request_ready_cb (SoupSession *session,
+			SoupMessage *msg,
+			gpointer     user_data)
+{
+	OAuthConnection    *self = user_data;
+	GSimpleAsyncResult *result;
+	SoupBuffer         *body;
+	GError             *error = NULL;
+
+	result = oauth_connection_get_result (self->priv->conn);
+
+	if (msg->status_code != 200) {
+		g_simple_async_result_set_error (result,
+						 SOUP_HTTP_ERROR,
+						 msg->status_code,
+						 "%s",
+						 soup_status_get_phrase (msg->status_code));
+		g_simple_async_result_complete_in_idle (result);
+		return;
+	}
+
+	body = soup_message_body_flatten (msg->response_body);
+	self->consumer->login_request_result (msg, body, result);
+	g_simple_async_result_complete_in_idle (result);
+
+	soup_buffer_free (body);
+}
+
+
+void
+oauth_connection_login_request (OAuthConnection     *self,
+				GCancellable        *cancellable,
+				GAsyncReadyCallback  callback,
+				gpointer             user_data)
+{
+	GHashTable  *data_set;
+	SoupMessage *msg;
+
+	gth_task_progress (GTH_TASK (self->priv->conn), _("Connecting to the server"), _("Getting account information"), TRUE, 0.0);
+
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+	oauth_connection_add_signature (self->priv->conn, "POST", self->consumer->login_request_uri, data_set);
+	msg = soup_form_request_new_from_hash ("POST", self->consumer->login_request_uri, data_set);
+	oauth_connection_send_message (self->priv->conn,
+				       msg,
+				       cancellable,
+				       callback,
+				       user_data,
+				       oauth_connection_login_request,
+				       login_request_ready_cb,
+				       self);
+
+	g_hash_table_destroy (data_set);
+}
+
+
+gboolean
+oauth_connection_login_request_finish (OAuthConnection  *self,
+				       GAsyncResult     *result,
+				       GError          **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return FALSE;
+	else
+		return TRUE;
+}
+
+
+char *
+oauth_connection_get_login_link (OAuthConnection *self)
+{
+
+}
+
+
+/* -- oauth_connection_get_access_token -- */
+
+
+void
+oauth_connection_get_access_token (OAuthConnection     *self,
+				   GCancellable        *cancellable,
+				   GAsyncReadyCallback  callback,
+				   gpointer             user_data)
+{
+}
+
+
+gboolean
+oauth_connection_get_access_token_finish (OAuthConnection  *self,
+					  GAsyncResult     *result,
+					  GError          **error)
+{
+
+}
+
+
+#if 0
+
+
+static void
+connection_frob_ready_cb (SoupSession *session,
+			  SoupMessage *msg,
+			  gpointer     user_data)
+{
+	OAuthConnection *self = user_data;
+	SoupBuffer       *body;
+	DomDocument      *doc = NULL;
+	GError           *error = NULL;
+
+	g_free (self->priv->frob);
+	self->priv->frob = NULL;
+
+	body = soup_message_body_flatten (msg->response_body);
+	if (oauth_utils_parse_response (body, &doc, &error)) {
+		DomElement *root;
+		DomElement *child;
+
+		root = DOM_ELEMENT (doc)->first_child;
+		for (child = root->first_child; child; child = child->next_sibling)
+			if (g_strcmp0 (child->tag_name, "frob") == 0)
+				self->priv->frob = g_strdup (dom_element_get_inner_text (child));
+
+		if (self->priv->frob == NULL) {
+			error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 0, _("Unknown error"));
+			g_simple_async_result_set_from_error (self->priv->result, error);
+		}
+		else
+			g_simple_async_result_set_op_res_gboolean (self->priv->result, TRUE);
+
+		g_object_unref (doc);
+	}
+	else
+		g_simple_async_result_set_from_error (self->priv->result, error);
+
+	g_simple_async_result_complete_in_idle (self->priv->result);
+
+	soup_buffer_free (body);
+}
+
+
+void
+oauth_connection_get_frob (OAuthConnection    *self,
+			    GCancellable        *cancellable,
+			    GAsyncReadyCallback  callback,
+			    gpointer             user_data)
+{
+	GHashTable  *data_set;
+	SoupMessage *msg;
+
+	gth_task_progress (GTH_TASK (self), _("Connecting to the server"), NULL, TRUE, 0.0);
+
+	g_free (self->priv->token);
+	self->priv->token = NULL;
+
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (data_set, "method", "oauth.auth.getFrob");
+	oauth_connection_add_api_sig (self, data_set);
+	msg = soup_form_request_new_from_hash ("GET", self->consumer->rest_url, data_set);
+	oauth_connection_send_message (self,
+					msg,
+					cancellable,
+					callback,
+					user_data,
+					oauth_connection_get_frob,
+					connection_frob_ready_cb,
+					self);
+
+	g_hash_table_destroy (data_set);
+}
+
+
+gboolean
+oauth_connection_get_frob_finish (OAuthConnection  *self,
+				   GAsyncResult      *result,
+				   GError           **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return FALSE;
+	else
+		return TRUE;
+}
+
+
+static char *
+get_access_type_name (OAuthAccessType access_type)
+{
+	char *name = NULL;
+
+	switch (access_type) {
+	case OAUTH_ACCESS_READ:
+		name = "read";
+		break;
+
+	case OAUTH_ACCESS_WRITE:
+		name = "write";
+		break;
+
+	case OAUTH_ACCESS_DELETE:
+		name = "delete";
+		break;
+	}
+
+	return name;
+}
+
+char *
+oauth_connection_get_login_link (OAuthConnection *self,
+				  OAuthAccessType  access_type)
+{
+	GHashTable *data_set;
+	GString    *link;
+	GList      *keys;
+	GList      *scan;
+
+	g_return_val_if_fail (self->priv->frob != NULL, NULL);
+
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (data_set, "frob", self->priv->frob);
+	g_hash_table_insert (data_set, "perms", get_access_type_name (access_type));
+	oauth_connection_add_api_sig (self, data_set);
+
+	link = g_string_new (self->consumer->authentication_url);
+	g_string_append (link, "?");
+	keys = g_hash_table_get_keys (data_set);
+	for (scan = keys; scan; scan = scan->next) {
+		char *key = scan->data;
+
+		if (scan != keys)
+			g_string_append (link, "&");
+		g_string_append (link, key);
+		g_string_append (link, "=");
+		g_string_append (link, g_hash_table_lookup (data_set, key));
+	}
+
+	g_list_free (keys);
+	g_hash_table_destroy (data_set);
+
+	return g_string_free (link, FALSE);
+}
+
+
+static void
+connection_token_ready_cb (SoupSession *session,
+			   SoupMessage *msg,
+			   gpointer     user_data)
+{
+	OAuthConnection *self = user_data;
+	SoupBuffer       *body;
+	DomDocument      *doc = NULL;
+	GError           *error = NULL;
+
+	body = soup_message_body_flatten (msg->response_body);
+	if (oauth_utils_parse_response (body, &doc, &error)) {
+		DomElement *response;
+		DomElement *auth;
+
+		response = DOM_ELEMENT (doc)->first_child;
+		for (auth = response->first_child; auth; auth = auth->next_sibling) {
+			if (g_strcmp0 (auth->tag_name, "auth") == 0) {
+				DomElement *node;
+
+				for (node = auth->first_child; node; node = node->next_sibling) {
+					if (g_strcmp0 (node->tag_name, "token") == 0) {
+						self->priv->token = g_strdup (dom_element_get_inner_text (node));
+					}
+					else if (g_strcmp0 (node->tag_name, "user") == 0) {
+						self->priv->username = g_strdup (dom_element_get_attribute (node, "username"));
+						self->priv->user_id = g_strdup (dom_element_get_attribute (node, "nsid"));
+					}
+				}
+			}
+		}
+
+		if (self->priv->token == NULL) {
+			error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 0, _("Unknown error"));
+			g_simple_async_result_set_from_error (self->priv->result, error);
+		}
+		else
+			g_simple_async_result_set_op_res_gboolean (self->priv->result, TRUE);
+
+		g_object_unref (doc);
+	}
+	else
+		g_simple_async_result_set_from_error (self->priv->result, error);
+
+	g_simple_async_result_complete_in_idle (self->priv->result);
+
+	soup_buffer_free (body);
+}
+
+
+void
+oauth_connection_get_token (OAuthConnection    *self,
+			     GCancellable        *cancellable,
+			     GAsyncReadyCallback  callback,
+			     gpointer             user_data)
+{
+	GHashTable  *data_set;
+	SoupMessage *msg;
+
+	gth_task_progress (GTH_TASK (self), _("Connecting to the server"), NULL, TRUE, 0.0);
+
+	g_free (self->priv->token);
+	self->priv->token = NULL;
+
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (data_set, "method", "oauth.auth.getToken");
+	g_hash_table_insert (data_set, "frob", self->priv->frob);
+	oauth_connection_add_api_sig (self, data_set);
+	msg = soup_form_request_new_from_hash ("GET", self->consumer->rest_url, data_set);
+	oauth_connection_send_message (self,
+					msg,
+					cancellable,
+					callback,
+					user_data,
+					oauth_connection_get_token,
+					connection_token_ready_cb,
+					self);
+
+	g_hash_table_destroy (data_set);
+}
+
+
+gboolean
+oauth_connection_get_token_finish (OAuthConnection  *self,
+				    GAsyncResult      *result,
+				    GError           **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return FALSE;
+	else
+		return TRUE;
+}
+
+
+void
+oauth_connection_set_auth_token (OAuthConnection *self,
+				  const char       *value)
+{
+	g_free (self->priv->token);
+	self->priv->token = NULL;
+	if (value != NULL)
+		self->priv->token = g_strdup (value);
+}
+
+
+const char *
+oauth_connection_get_auth_token (OAuthConnection *self)
+{
+	return self->priv->token;
+}
+
+
+const char *
+oauth_connection_get_username (OAuthConnection *self)
+{
+	return self->priv->username;
+}
+
+
+const char *
+oauth_connection_get_user_id (OAuthConnection *self)
+{
+	return self->priv->user_id;
+}
+
+
+/* utilities */
+
+
+gboolean
+oauth_utils_parse_response (SoupBuffer   *body,
+			     DomDocument **doc_p,
+			     GError      **error)
+{
+	DomDocument *doc;
+	DomElement  *node;
+
+	doc = dom_document_new ();
+	if (! dom_document_load (doc, body->data, body->length, error)) {
+		g_object_unref (doc);
+		return FALSE;
+	}
+
+	for (node = DOM_ELEMENT (doc)->first_child; node; node = node->next_sibling) {
+		if (g_strcmp0 (node->tag_name, "rsp") == 0) {
+			if (g_strcmp0 (dom_element_get_attribute (node, "stat"), "ok") != 0) {
+				DomElement *child;
+
+				for (child = node->first_child; child; child = child->next_sibling) {
+					if (g_strcmp0 (child->tag_name, "err") == 0) {
+						*error = g_error_new_literal (OAUTH_CONNECTION_ERROR,
+									      atoi (dom_element_get_attribute (child, "code")),
+									      dom_element_get_attribute (child, "msg"));
+					}
+				}
+
+				g_object_unref (doc);
+				return FALSE;
+			}
+		}
+	}
+
+	*doc_p = doc;
+
+	return TRUE;
+}
+
+#endif
diff --git a/extensions/oauth/oauth-connection.h b/extensions/oauth/oauth-connection.h
new file mode 100644
index 0000000..b32099c
--- /dev/null
+++ b/extensions/oauth/oauth-connection.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef OAUTH_CONNECTION_H
+#define OAUTH_CONNECTION_H
+
+#include <glib-object.h>
+#ifdef HAVE_LIBSOUP_GNOME
+#include <libsoup/soup-gnome.h>
+#else
+#include <libsoup/soup.h>
+#endif /* HAVE_LIBSOUP_GNOME */
+#include <gthumb.h>
+#include "oauth-types.h"
+
+#define OAUTH_CONNECTION_ERROR oauth_connection_error_quark ()
+GQuark oauth_connection_error_quark (void);
+
+#define OAUTH_CONNECTION_ERROR_INVALID_TOKEN 100
+
+#define OAUTH_TYPE_CONNECTION         (oauth_connection_get_type ())
+#define OAUTH_CONNECTION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), OAUTH_TYPE_CONNECTION, OAuthConnection))
+#define OAUTH_CONNECTION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), OAUTH_TYPE_CONNECTION, OAuthConnectionClass))
+#define OAUTH_IS_CONNECTION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), OAUTH_TYPE_CONNECTION))
+#define OAUTH_IS_CONNECTION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), OAUTH_TYPE_CONNECTION))
+#define OAUTH_CONNECTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), OAUTH_TYPE_CONNECTION, OAuthConnectionClass))
+
+typedef struct _OAuthConnection         OAuthConnection;
+typedef struct _OAuthConnectionPrivate  OAuthConnectionPrivate;
+typedef struct _OAuthConnectionClass    OAuthConnectionClass;
+
+struct _OAuthConnection
+{
+	GthTask __parent;
+	OAuthConsumer *consumer;
+	OAuthConnectionPrivate *priv;
+};
+
+struct _OAuthConnectionClass
+{
+	GthTaskClass __parent_class;
+};
+
+GType                oauth_connection_get_type                 (void) G_GNUC_CONST;
+OAuthConnection *    oauth_connection_new                      (OAuthConsumer        *consumer);
+void		     oauth_connection_send_message             (OAuthConnection      *self,
+						                SoupMessage          *msg,
+						                GCancellable         *cancellable,
+						                GAsyncReadyCallback   callback,
+						                gpointer              user_data,
+						                gpointer              source_tag,
+						                SoupSessionCallback   soup_session_cb,
+						                gpointer              soup_session_cb_data);
+GSimpleAsyncResult * oauth_connection_get_result               (OAuthConnection      *self);
+void                 oauth_connection_reset_result             (OAuthConnection      *self);
+void                 oauth_connection_add_signature            (OAuthConnection      *self,
+								const char           *method,
+								const char           *url,
+								GHashTable           *parameters);
+void                 oauth_connection_login_request            (OAuthConnection      *self,
+							        GCancellable         *cancellable,
+							        GAsyncReadyCallback   callback,
+							        gpointer              user_data);
+gboolean             oauth_connection_login_request_finish     (OAuthConnection      *self,
+						                GAsyncResult         *result,
+						                GError              **error);
+char *               oauth_connection_get_login_link           (OAuthConnection      *self);
+void                 oauth_connection_get_access_token         (OAuthConnection      *self,
+								GCancellable         *cancellable,
+							        GAsyncReadyCallback   callback,
+							        gpointer              user_data);
+gboolean             oauth_connection_get_access_token_finish  (OAuthConnection      *self,
+								GAsyncResult         *result,
+								GError              **error);
+void                 oauth_connection_set_token                (OAuthConnection      *self,
+							        const char           *token,
+								const char           *token_secret);
+const char *         oauth_connection_get_username             (OAuthConnection      *self);
+const char *         oauth_connection_get_token                (OAuthConnection      *self);
+const char *         oauth_connection_get_token_secret         (OAuthConnection      *self);
+
+#if 0
+void                 oauth_connection_get_frob           (OAuthConnection       *self,
+						           GCancellable         *cancellable,
+						           GAsyncReadyCallback   callback,
+						           gpointer              user_data);
+gboolean             oauth_connection_get_frob_finish    (OAuthConnection       *self,
+							   GAsyncResult         *result,
+							   GError              **error);
+char *               oauth_connection_get_login_link     (OAuthConnection       *self,
+							   OAuthAccessType       access_type);
+void                 oauth_connection_get_token          (OAuthConnection       *self,
+						           GCancellable         *cancellable,
+						           GAsyncReadyCallback   callback,
+						           gpointer              user_data);
+gboolean             oauth_connection_get_token_finish   (OAuthConnection       *self,
+							   GAsyncResult         *result,
+							   GError              **error);
+void                 oauth_connection_set_auth_token     (OAuthConnection       *self,
+							   const char           *value);
+const char *         oauth_connection_get_auth_token     (OAuthConnection       *self);
+const char *         oauth_connection_get_username       (OAuthConnection       *self);
+const char *         oauth_connection_get_user_id        (OAuthConnection       *self);
+
+/* utilities */
+
+gboolean             oauth_utils_parse_response          (SoupBuffer            *body,
+							  DomDocument          **doc_p,
+							  GError               **error);
+#endif
+
+#endif /* OAUTH_CONNECTION_H */
diff --git a/extensions/oauth/oauth-types.h b/extensions/oauth/oauth-types.h
new file mode 100644
index 0000000..18218ea
--- /dev/null
+++ b/extensions/oauth/oauth-types.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef OAUTH_TYPES_H
+#define OAUTH_TYPES_H
+
+typedef struct {
+	const char *name;
+	const char *url;
+	const char *protocol;
+	const char *request_token_url;
+	const char *user_authorization_url;
+	const char *access_token_url;
+	const char *consumer_key;
+	const char *consumer_secret;
+	const char *login_request_uri;
+	void      (* login_request_result) (SoupMessage        *msg,
+					    SoupBuffer         *body,
+					    GSimpleAsyncResult *result);
+} OAuthConsumer;
+
+#endif /* OAUTH_TYPES_H */
diff --git a/extensions/oauth/oauth.extension.in.in b/extensions/oauth/oauth.extension.in.in
new file mode 100644
index 0000000..bb60f9b
--- /dev/null
+++ b/extensions/oauth/oauth.extension.in.in
@@ -0,0 +1,6 @@
+[Extension]
+Hidden=true
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/photobucket/Makefile.am b/extensions/photobucket/Makefile.am
new file mode 100644
index 0000000..2cf881b
--- /dev/null
+++ b/extensions/photobucket/Makefile.am
@@ -0,0 +1,57 @@
+if ENABLE_WEB_SERVICES
+
+SUBDIRS = data
+
+extensiondir = $(pkglibdir)/extensions
+extension_LTLIBRARIES = libphotobucket.la
+
+libphotobucket_la_SOURCES = 			\
+	actions.c				\
+	actions.h				\
+	callbacks.c				\
+	callbacks.h				\
+	dlg-export-to-photobucket.c		\
+	dlg-export-to-photobucket.h		\
+	photobucket-account.c			\
+	photobucket-account.h			\
+	photobucket-account-chooser-dialog.c	\
+	photobucket-account-chooser-dialog.h	\
+	photobucket-account-manager-dialog.c	\
+	photobucket-account-manager-dialog.h	\
+	photobucket-authentication.c		\
+	photobucket-authentication.h		\
+	photobucket-connection.c		\
+	photobucket-connection.h		\
+	photobucket-photo.c			\
+	photobucket-photo.h			\
+	photobucket-photoset.c			\
+	photobucket-photoset.h			\
+	photobucket-service.c			\
+	photobucket-service.h			\
+	photobucket-types.h			\
+	photobucket-user.c			\
+	photobucket-user.h			\
+	main.c
+
+libphotobucket_la_CFLAGS = $(GTHUMB_CFLAGS) $(LIBSOUP_CFLAGS) $(GNOME_KEYRING_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
+libphotobucket_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libphotobucket_la_LIBADD = $(GTHUMB_LIBS) $(LIBSOUP_LIBS) $(GNOME_KEYRING_LIBS) ../oauth/liboauth.la
+libphotobucket_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = photobucket.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+	$(AM_V_GEN)( sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+	$< > $@ )
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files) 
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/photobucket/actions.c b/extensions/photobucket/actions.c
new file mode 100644
index 0000000..134ea91
--- /dev/null
+++ b/extensions/photobucket/actions.c
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gthumb.h>
+#include "dlg-export-to-photobucket.h"
+
+
+void
+gth_browser_activate_action_export_photobucket (GtkAction  *action,
+						GthBrowser *browser)
+{
+	GList *items;
+	GList *file_list;
+
+	items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+	file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
+	if (file_list == NULL)
+		file_list = gth_file_store_get_visibles (gth_browser_get_file_store (browser));
+	dlg_export_to_photobucket (browser, file_list);
+
+	_g_object_list_unref (file_list);
+	_gtk_tree_path_list_free (items);
+}
diff --git a/extensions/photobucket/actions.h b/extensions/photobucket/actions.h
new file mode 100644
index 0000000..5baa5c3
--- /dev/null
+++ b/extensions/photobucket/actions.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ACTIONS_H
+#define ACTIONS_H
+
+#include <gtk/gtk.h>
+
+#define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
+
+DEFINE_ACTION(gth_browser_activate_action_export_photobucket)
+
+#endif /* ACTIONS_H */
diff --git a/extensions/photobucket/callbacks.c b/extensions/photobucket/callbacks.c
new file mode 100644
index 0000000..1180c90
--- /dev/null
+++ b/extensions/photobucket/callbacks.c
@@ -0,0 +1,94 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <gthumb.h>
+#include "actions.h"
+
+
+#define BROWSER_DATA_KEY "photobucket-browser-data"
+
+
+static const char *ui_info =
+"<ui>"
+"  <menubar name='MenuBar'>"
+"    <menu name='File' action='FileMenu'>"
+"      <menu name='Export' action='ExportMenu'>"
+"        <placeholder name='Web_Services'>"
+"          <menuitem action='File_Export_PhotoBucket'/>"
+"        </placeholder>"
+"      </menu>"
+"    </menu>"
+"  </menubar>"
+"</ui>";
+
+
+static GtkActionEntry action_entries[] = {
+	{ "File_Export_PhotoBucket", "photobucket",
+	  N_("PhotoBucket..."), NULL,
+	  N_("Upload photos to PhotoBucket"),
+	  G_CALLBACK (gth_browser_activate_action_export_photobucket) },
+};
+
+
+typedef struct {
+	GtkActionGroup *action_group;
+} BrowserData;
+
+
+static void
+browser_data_free (BrowserData *data)
+{
+	g_free (data);
+}
+
+
+void
+fl__gth_browser_construct_cb (GthBrowser *browser)
+{
+	BrowserData *data;
+	GError      *error = NULL;
+	guint        merge_id;
+
+	g_return_if_fail (GTH_IS_BROWSER (browser));
+
+	data = g_new0 (BrowserData, 1);
+
+	data->action_group = gtk_action_group_new ("PhotoBucket Actions");
+	gtk_action_group_set_translation_domain (data->action_group, NULL);
+	gtk_action_group_add_actions (data->action_group,
+				      action_entries,
+				      G_N_ELEMENTS (action_entries),
+				      browser);
+	gtk_ui_manager_insert_action_group (gth_browser_get_ui_manager (browser), data->action_group, 0);
+
+	merge_id = gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), ui_info, -1, &error);
+	if (merge_id == 0) {
+		g_warning ("building ui failed: %s", error->message);
+		g_clear_error (&error);
+	}
+
+	g_object_set_data_full (G_OBJECT (browser), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
+}
diff --git a/extensions/photobucket/callbacks.h b/extensions/photobucket/callbacks.h
new file mode 100644
index 0000000..edc9e6e
--- /dev/null
+++ b/extensions/photobucket/callbacks.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CALLBACKS_H
+#define CALLBACKS_H
+
+#include <gthumb.h>
+
+void pb__gth_browser_construct_cb (GthBrowser *browser);
+
+#endif /* CALLBACKS_H */
diff --git a/extensions/photobucket/data/Makefile.am b/extensions/photobucket/data/Makefile.am
new file mode 100644
index 0000000..c1713cf
--- /dev/null
+++ b/extensions/photobucket/data/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = ui
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/photobucket/data/ui/Makefile.am b/extensions/photobucket/data/ui/Makefile.am
new file mode 100644
index 0000000..e95fff4
--- /dev/null
+++ b/extensions/photobucket/data/ui/Makefile.am
@@ -0,0 +1,8 @@
+uidir = $(pkgdatadir)/ui
+ui_DATA = 					\
+	export-to-photobucket.ui		\
+	photobucket-export-completed.ui		
+
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/photobucket/data/ui/export-to-photobucket.ui b/extensions/photobucket/data/ui/export-to-photobucket.ui
new file mode 100644
index 0000000..e9e12e5
--- /dev/null
+++ b/extensions/photobucket/data/ui/export-to-photobucket.ui
@@ -0,0 +1,427 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkDialog" id="export_dialog">
+    <property name="border_width">5</property>
+    <property name="type_hint">dialog</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox7">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkVBox" id="hbox1">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkVBox" id="images_box">
+                <property name="width_request">460</property>
+                <property name="height_request">340</property>
+                <property name="visible">True</property>
+                <property name="spacing">3</property>
+                <child>
+                  <object class="GtkLabel" id="images_info_label">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="vbox1">
+                <property name="visible">True</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkTable" id="table2">
+                    <property name="visible">True</property>
+                    <property name="n_rows">5</property>
+                    <property name="n_columns">2</property>
+                    <property name="column_spacing">6</property>
+                    <property name="row_spacing">5</property>
+                    <child>
+                      <object class="GtkAlignment" id="alignment1">
+                        <property name="visible">True</property>
+                        <property name="top_padding">6</property>
+                        <child>
+                          <object class="GtkLabel" id="label3">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="label" translatable="yes">_Account:</property>
+                            <property name="use_underline">True</property>
+                            <property name="mnemonic_widget">account_combobox</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkVBox" id="vbox4">
+                        <property name="visible">True</property>
+                        <property name="spacing">3</property>
+                        <child>
+                          <object class="GtkHBox" id="hbox4">
+                            <property name="visible">True</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkComboBox" id="account_combobox">
+                                <property name="width_request">300</property>
+                                <property name="visible">True</property>
+                                <property name="model">account_liststore</property>
+                                <child>
+                                  <object class="GtkCellRendererText" id="cellrenderertext4"/>
+                                  <attributes>
+                                    <attribute name="text">1</attribute>
+                                  </attributes>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="edit_accounts_button">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">True</property>
+                                <property name="tooltip_text" translatable="yes">Edit accounts</property>
+                                <child>
+                                  <object class="GtkImage" id="image2">
+                                    <property name="visible">True</property>
+                                    <property name="stock">gtk-edit</property>
+                                  </object>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkHBox" id="hbox3">
+                            <property name="visible">True</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel" id="label4">
+                                <property name="visible">True</property>
+                                <property name="label" translatable="yes">Free space:</property>
+                                <attributes>
+                                  <attribute name="absolute-size" value="10000"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="free_space_label">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="yalign">0.51999998092651367</property>
+                                <attributes>
+                                  <attribute name="absolute-size" value="10000"/>
+                                </attributes>
+                              </object>
+                              <packing>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label1">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Ph_otoset:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">photoset_comboboxentry</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBoxEntry" id="photoset_comboboxentry">
+                        <property name="visible">True</property>
+                        <property name="model">photoset_liststore</property>
+                        <property name="text_column">2</property>
+                        <child>
+                          <object class="GtkCellRendererText" id="cellrenderertext2"/>
+                          <attributes>
+                            <attribute name="text">3</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label5">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">_Privacy:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">privacy_combobox</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="privacy_combobox">
+                        <property name="visible">True</property>
+                        <property name="model">privacy_liststore</property>
+                        <property name="active">0</property>
+                        <child>
+                          <object class="GtkCellRendererText" id="cellrenderertext5"/>
+                          <attributes>
+                            <attribute name="text">1</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkCheckButton" id="hidden_checkbutton">
+                        <property name="label" translatable="yes">Hi_de from public searches</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="draw_indicator">True</property>
+                      </object>
+                      <packing>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">4</property>
+                        <property name="bottom_attach">5</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label2">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">_Safety:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">safety_combobox</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">3</property>
+                        <property name="bottom_attach">4</property>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="safety_combobox">
+                        <property name="visible">True</property>
+                        <property name="model">safety_liststore</property>
+                        <property name="active">0</property>
+                        <child>
+                          <object class="GtkCellRendererText" id="cellrenderertext3"/>
+                          <attributes>
+                            <attribute name="text">1</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">3</property>
+                        <property name="bottom_attach">4</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area7">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="close_button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="upload_button">
+                <property name="label" translatable="yes">_Upload</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="image">upload_image</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button1">
+                <property name="label">gtk-help</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">close_button</action-widget>
+      <action-widget response="-5">upload_button</action-widget>
+      <action-widget response="-11">button1</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkImage" id="upload_image">
+    <property name="visible">True</property>
+    <property name="stock">gtk-goto-top</property>
+  </object>
+  <object class="GtkListStore" id="account_liststore">
+    <columns>
+      <!-- column-name account -->
+      <column type="GObject"/>
+      <!-- column-name username -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkListStore" id="safety_liststore">
+    <columns>
+      <!-- column-name value -->
+      <column type="gint"/>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0">0</col>
+        <col id="1" translatable="yes">Safe content</col>
+      </row>
+      <row>
+        <col id="0">1</col>
+        <col id="1" translatable="yes">Moderate content</col>
+      </row>
+      <row>
+        <col id="0">2</col>
+        <col id="1" translatable="yes">Restricted content</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkListStore" id="privacy_liststore">
+    <columns>
+      <!-- column-name value -->
+      <column type="gint"/>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0">0</col>
+        <col id="1" translatable="yes">Public photos</col>
+      </row>
+      <row>
+        <col id="0">1</col>
+        <col id="1" translatable="yes">Private photos, visible to family and friends</col>
+      </row>
+      <row>
+        <col id="0">2</col>
+        <col id="1" translatable="yes">Private photos, visible to friends</col>
+      </row>
+      <row>
+        <col id="0">3</col>
+        <col id="1" translatable="yes">Private photos, visible to family</col>
+      </row>
+      <row>
+        <col id="0">4</col>
+        <col id="1" translatable="yes">Private photos</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkListStore" id="photoset_liststore">
+    <columns>
+      <!-- column-name data -->
+      <column type="GObject"/>
+      <!-- column-name icon -->
+      <column type="gchararray"/>
+      <!-- column-name title -->
+      <column type="gchararray"/>
+      <!-- column-name n_photos -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+</interface>
diff --git a/extensions/photobucket/data/ui/photobucket-export-completed.ui b/extensions/photobucket/data/ui/photobucket-export-completed.ui
new file mode 100644
index 0000000..7de41da
--- /dev/null
+++ b/extensions/photobucket/data/ui/photobucket-export-completed.ui
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkMessageDialog" id="completed_messagedialog">
+    <property name="border_width">5</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="type_hint">normal</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="text" translatable="yes">Files successfully uploaded to the server.</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button1">
+                <property name="label" translatable="yes">_Open in the Browser</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="image">icon_image</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button2">
+                <property name="label">gtk-close</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="1">button1</action-widget>
+      <action-widget response="-7">button2</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkImage" id="icon_image">
+    <property name="visible">True</property>
+    <property name="yalign">0</property>
+    <property name="stock">gtk-jump-to</property>
+  </object>
+</interface>
diff --git a/extensions/photobucket/dlg-export-to-photobucket.c b/extensions/photobucket/dlg-export-to-photobucket.c
new file mode 100644
index 0000000..601ad00
--- /dev/null
+++ b/extensions/photobucket/dlg-export-to-photobucket.c
@@ -0,0 +1,591 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 The Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "dlg-export-to-photobucket.h"
+#include "photobucket-service.h"
+
+
+#define GET_WIDGET(x) (_gtk_builder_get_widget (data->builder, (x)))
+#define _OPEN_IN_BROWSER_RESPONSE 1
+
+
+enum {
+	ACCOUNT_DATA_COLUMN,
+	ACCOUNT_NAME_COLUMN
+};
+
+
+enum {
+	ALBUM_DATA_COLUMN,
+	ALBUM_ICON_COLUMN,
+	ALBUM_TITLE_COLUMN,
+	ALBUM_N_PHOTOS_COLUMN
+};
+
+
+static OAuthServer photobucket_server = {
+	"PhotoBucket",
+	"http://www.photobucket.com";,
+	"http://www.flickr.com/services/auth/";,
+	"http://api.flickr.com/services/rest";,
+	"http://api.flickr.com/services/upload/";,
+	"8960706ee7f4151e893b11837e9c24ce",
+	"1ff8d1e45c873423"
+};
+
+
+typedef struct {
+	GthBrowser          *browser;
+	GthFileData         *location;
+	GList               *file_list;
+	GtkBuilder          *builder;
+	GtkWidget           *dialog;
+	GtkWidget           *progress_dialog;
+	OAuthConnection     *conn;
+	PhotobucketService  *service;
+	OAuthAuthentication *auth;
+	PhotobucketUser     *user;
+	GList               *albums;
+	PhotobucketAlbum    *album;
+	GList               *photos_ids;
+	GCancellable        *cancellable;
+} DialogData;
+
+
+static void
+export_dialog_destroy_cb (GtkWidget  *widget,
+			  DialogData *data)
+{
+	if (data->conn != NULL)
+		gth_task_completed (GTH_TASK (data->conn), NULL);
+	_g_object_unref (data->cancellable);
+	_g_string_list_free (data->photos_ids);
+	_g_object_unref (data->album);
+	_g_object_list_unref (data->albums);
+	_g_object_unref (data->user);
+	_g_object_unref (data->auth);
+	_g_object_unref (data->service);
+	_g_object_unref (data->conn);
+	_g_object_unref (data->builder);
+	_g_object_list_unref (data->file_list);
+	_g_object_unref (data->location);
+	g_free (data);
+}
+
+
+static void
+completed_messagedialog_response_cb (GtkDialog *dialog,
+				     int        response_id,
+				     gpointer   user_data)
+{
+	DialogData *data = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CLOSE:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gtk_widget_destroy (data->dialog);
+		break;
+
+	case _OPEN_IN_BROWSER_RESPONSE:
+		{
+			char   *url = NULL;
+			GError *error = NULL;
+
+			gtk_widget_destroy (GTK_WIDGET (dialog));
+
+			if (data->album == NULL) {
+				GString *ids;
+				GList   *scan;
+
+				ids = g_string_new ("");
+				for (scan = data->photos_ids; scan; scan = scan->next) {
+					if (scan != data->photos_ids)
+						g_string_append (ids, ",");
+					g_string_append (ids, (char *) scan->data);
+				}
+				url = g_strconcat (data->server->url, "/photos/upload/edit/?ids=", ids->str, NULL);
+
+				g_string_free (ids, TRUE);
+			}
+			else if (data->album->url != NULL)
+				url = g_strdup (data->album->url);
+			else if (data->album->id != NULL)
+				url = g_strconcat (data->server->url, "/photos/", data->user->id, "/sets/", data->album->id, NULL);
+
+			if ((url != NULL) && ! gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (dialog)), url, 0, &error)) {
+				if (data->conn != NULL)
+					gth_task_dialog (GTH_TASK (data->conn), TRUE);
+				_gtk_error_dialog_from_gerror_run (GTK_WINDOW (data->browser), _("Could not connect to the server"), &error);
+			}
+			gtk_widget_destroy (data->dialog);
+
+			g_free (url);
+		}
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+export_completed_with_success (DialogData *data)
+{
+	GtkBuilder *builder;
+	GtkWidget  *dialog;
+
+	gth_task_dialog (GTH_TASK (data->conn), TRUE);
+
+	builder = _gtk_builder_new_from_file ("photobucket-export-completed.ui", "photobucket");
+	dialog = _gtk_builder_get_widget (builder, "completed_messagedialog");
+	g_object_set_data_full (G_OBJECT (dialog), "builder", builder, g_object_unref);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (completed_messagedialog_response_cb),
+			  data);
+
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (data->browser));
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_present (GTK_WINDOW (dialog));
+}
+
+
+static void
+add_photos_to_album_ready_cb (GObject      *source_object,
+				 GAsyncResult *result,
+				 gpointer      user_data)
+{
+	DialogData *data = user_data;
+	GError     *error = NULL;
+
+	if (! photobucket_service_add_photos_to_set_finish (PHOTOBUCKET_SERVICE (source_object), result, &error)) {
+		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Could not create the album"), &error);
+		gtk_widget_destroy (data->dialog);
+		return;
+	}
+
+	export_completed_with_success (data);
+}
+
+
+static void
+add_photos_to_album (DialogData *data)
+{
+	photobucket_service_add_photos_to_set (data->service,
+					  data->album,
+					  data->photos_ids,
+					  data->cancellable,
+					  add_photos_to_album_ready_cb,
+					  data);
+}
+
+
+static void
+create_album_ready_cb (GObject      *source_object,
+			  GAsyncResult *result,
+			  gpointer      user_data)
+{
+	DialogData *data = user_data;
+	GError     *error = NULL;
+	char       *primary;
+
+	primary = g_strdup (data->album->primary);
+	g_object_unref (data->album);
+	data->album = photobucket_service_create_album_finish (PHOTOBUCKET_SERVICE (source_object), result, &error);
+	if (error != NULL) {
+		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Could not create the album"), &error);
+		gtk_widget_destroy (data->dialog);
+	}
+	else {
+		photobucket_album_set_primary (data->album, primary);
+		add_photos_to_album (data);
+	}
+
+	g_free (primary);
+}
+
+
+static void
+post_photos_ready_cb (GObject      *source_object,
+		      GAsyncResult *result,
+		      gpointer      user_data)
+{
+	DialogData *data = user_data;
+	GError     *error = NULL;
+
+	data->photos_ids = photobucket_service_post_photos_finish (PHOTOBUCKET_SERVICE (source_object), result, &error);
+	if (error != NULL) {
+		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Could not upload the files"), &error);
+		gtk_widget_destroy (data->dialog);
+		return;
+	}
+
+	if (data->album == NULL) {
+		export_completed_with_success (data);
+		return;
+	}
+
+	/* create the album if it doesn't exists */
+
+	if (data->album->id == NULL) {
+		char *first_id;
+
+		first_id = data->photos_ids->data;
+		photobucket_album_set_primary (data->album, first_id);
+		photobucket_service_create_album (data->service,
+						data->album,
+						data->cancellable,
+						create_album_ready_cb,
+						data);
+	}
+	else
+		add_photos_to_album (data);
+}
+
+
+static int
+find_album_by_title (PhotobucketAlbum *album,
+		        const char     *name)
+{
+	return g_strcmp0 (album->title, name);
+}
+
+
+static void
+export_dialog_response_cb (GtkDialog *dialog,
+			   int        response_id,
+			   gpointer   user_data)
+{
+	DialogData *data = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_HELP:
+		show_help_dialog (GTK_WINDOW (data->browser), "export-to-photobucket");
+		break;
+
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (data->dialog);
+		break;
+
+	case GTK_RESPONSE_OK:
+		{
+			char  *album_title;
+			GList *file_list;
+
+			gtk_widget_hide (data->dialog);
+			gth_task_dialog (GTH_TASK (data->conn), FALSE);
+
+			data->album = NULL;
+			album_title = gtk_combo_box_get_active_text (GTK_COMBO_BOX (GET_WIDGET ("album_comboboxentry")));
+			if ((album_title != NULL) && (g_strcmp0 (album_title, "") != 0)) {
+				GList *link;
+
+				link = g_list_find_custom (data->albums, album_title, (GCompareFunc) find_album_by_title);
+				if (link != NULL)
+					data->album = g_object_ref (link->data);
+
+				if (data->album == NULL) {
+					data->album = photobucket_album_new ();
+					photobucket_album_set_title (data->album, album_title);
+				}
+			}
+
+			file_list = gth_file_data_list_to_file_list (data->file_list);
+			photobucket_service_post_photos (data->service,
+						    gtk_combo_box_get_active (GTK_COMBO_BOX (GET_WIDGET ("privacy_combobox"))),
+						    gtk_combo_box_get_active (GTK_COMBO_BOX (GET_WIDGET ("safety_combobox"))),
+						    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("hidden_checkbutton"))),
+						    file_list,
+						    data->cancellable,
+						    post_photos_ready_cb,
+						    data);
+
+			_g_object_list_unref (file_list);
+			g_free (album_title);
+		}
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+update_account_list (DialogData *data)
+{
+	int            current_account_idx;
+	PhotobucketAccount *current_account;
+	int            idx;
+	GList         *scan;
+	GtkTreeIter    iter;
+	char          *free_space;
+
+	gtk_list_store_clear (GTK_LIST_STORE (GET_WIDGET ("account_liststore")));
+
+	current_account_idx = 0;
+	current_account = photobucket_authentication_get_account (data->auth);
+	for (scan = photobucket_authentication_get_accounts (data->auth), idx = 0; scan; scan = scan->next, idx++) {
+		PhotobucketAccount *account = scan->data;
+
+		if ((current_account != NULL) && (g_strcmp0 (current_account->username, account->username) == 0))
+			current_account_idx = idx;
+
+		gtk_list_store_append (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter);
+		gtk_list_store_set (GTK_LIST_STORE (GET_WIDGET ("account_liststore")), &iter,
+				    ACCOUNT_DATA_COLUMN, account,
+				    ACCOUNT_NAME_COLUMN, account->username,
+				    -1);
+	}
+	gtk_combo_box_set_active (GTK_COMBO_BOX (GET_WIDGET ("account_combobox")), current_account_idx);
+
+	free_space = g_format_size_for_display (data->user->max_bandwidth - data->user->used_bandwidth);
+	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("free_space_label")), free_space);
+	g_free (free_space);
+}
+
+
+static void
+authentication_accounts_changed_cb (PhotobucketAuthentication *auth,
+				    gpointer              user_data)
+{
+	update_account_list ((DialogData *) user_data);
+}
+
+
+static void
+album_list_ready_cb (GObject      *source_object,
+			GAsyncResult *res,
+			gpointer      user_data)
+{
+	DialogData *data = user_data;
+	GError     *error = NULL;
+	GList      *scan;
+
+	_g_object_list_unref (data->albums);
+	data->albums = photobucket_service_list_albums_finish (PHOTOBUCKET_SERVICE (source_object), res, &error);
+	if (error != NULL) {
+		if (data->conn != NULL)
+			gth_task_dialog (GTH_TASK (data->conn), TRUE);
+		_gtk_error_dialog_from_gerror_run (GTK_WINDOW (data->browser), _("Could not connect to the server"), &error);
+		gtk_widget_destroy (data->dialog);
+		return;
+	}
+
+	gtk_list_store_clear (GTK_LIST_STORE (GET_WIDGET ("album_liststore")));
+	for (scan = data->albums; scan; scan = scan->next) {
+		PhotobucketAlbum *album = scan->data;
+		char             *n_photos;
+		GtkTreeIter       iter;
+
+		n_photos = g_strdup_printf ("(%d)", album->n_photos);
+
+		gtk_list_store_append (GTK_LIST_STORE (GET_WIDGET ("album_liststore")), &iter);
+		gtk_list_store_set (GTK_LIST_STORE (GET_WIDGET ("album_liststore")), &iter,
+				    ALBUM_DATA_COLUMN, album,
+				    ALBUM_ICON_COLUMN, "file-catalog",
+				    ALBUM_TITLE_COLUMN, album->title,
+				    ALBUM_N_PHOTOS_COLUMN, n_photos,
+				    -1);
+
+		g_free (n_photos);
+	}
+
+	gtk_widget_set_sensitive (GET_WIDGET ("upload_button"), TRUE);
+
+	gth_task_dialog (GTH_TASK (data->conn), TRUE);
+
+	gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (data->browser));
+	gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
+	gtk_window_present (GTK_WINDOW (data->dialog));
+}
+
+
+static void
+authentication_ready_cb (PhotobucketAuthentication *auth,
+			 PhotobucketUser           *user,
+			 DialogData           *data)
+{
+	_g_object_unref (data->user);
+	data->user = g_object_ref (user);
+	update_account_list (data);
+
+	photobucket_service_list_albums (data->service,
+				       NULL,
+				       data->cancellable,
+				       album_list_ready_cb,
+				       data);
+}
+
+
+static void
+edit_accounts_button_clicked_cb (GtkButton  *button,
+				 DialogData *data)
+{
+	photobucket_authentication_edit_accounts (data->auth, GTK_WINDOW (data->dialog));
+}
+
+
+static void
+account_combobox_changed_cb (GtkComboBox *widget,
+			     gpointer     user_data)
+{
+	DialogData    *data = user_data;
+	GtkTreeIter    iter;
+	PhotobucketAccount *account;
+
+	if (! gtk_combo_box_get_active_iter (widget, &iter))
+		return;
+
+	gtk_tree_model_get (gtk_combo_box_get_model (widget),
+			    &iter,
+			    ACCOUNT_DATA_COLUMN, &account,
+			    -1);
+
+	if (photobucket_account_cmp (account, photobucket_authentication_get_account (data->auth)) != 0)
+		photobucket_authentication_connect (data->auth, account);
+
+	g_object_unref (account);
+}
+
+
+void
+dlg_export_to_photobucket (GthBrowser *browser,
+			   GList      *file_list)
+{
+	DialogData *data;
+	GList      *scan;
+	int         n_total;
+	goffset     total_size;
+	char       *total_size_formatted;
+	char       *text;
+	GtkWidget  *list_view;
+	char       *title;
+
+	data = g_new0 (DialogData, 1);
+	data->server = server;
+	data->browser = browser;
+	data->location = gth_file_data_dup (gth_browser_get_location_data (browser));
+	data->builder = _gtk_builder_new_from_file ("export-to-photobucket.ui", "photobucket");
+	data->dialog = _gtk_builder_get_widget (data->builder, "export_dialog");
+	data->cancellable = g_cancellable_new ();
+
+	data->file_list = NULL;
+	n_total = 0;
+	total_size = 0;
+	for (scan = file_list; scan; scan = scan->next) {
+		GthFileData *file_data = scan->data;
+		const char  *mime_type;
+
+		mime_type = gth_file_data_get_mime_type (file_data);
+		if (g_content_type_equals (mime_type, "image/bmp")
+		    || g_content_type_equals (mime_type, "image/gif")
+		    || g_content_type_equals (mime_type, "image/jpeg")
+		    || g_content_type_equals (mime_type, "image/png"))
+		{
+			total_size += g_file_info_get_size (file_data->info);
+			n_total++;
+			data->file_list = g_list_prepend (data->file_list, g_object_ref (file_data));
+		}
+	}
+	data->file_list = g_list_reverse (data->file_list);
+
+	if (data->file_list == NULL) {
+		GError *error;
+
+		error = g_error_new_literal (GTH_ERROR, GTH_ERROR_GENERIC, _("No valid file selected."));
+		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (browser), _("Could not export the files"), &error);
+		gtk_widget_destroy (data->dialog);
+
+		return;
+	}
+
+	total_size_formatted = g_format_size_for_display (total_size);
+	text = g_strdup_printf (g_dngettext (NULL, "%d file (%s)", "%d files (%s)", n_total), n_total, total_size_formatted);
+	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("images_info_label")), text);
+	g_free (text);
+	g_free (total_size_formatted);
+
+	/* Set the widget data */
+
+	list_view = gth_file_list_new (GTH_FILE_LIST_TYPE_NO_SELECTION);
+	gth_file_list_set_thumb_size (GTH_FILE_LIST (list_view), 112);
+	gth_file_view_set_spacing (GTH_FILE_VIEW (gth_file_list_get_view (GTH_FILE_LIST (list_view))), 0);
+	gth_file_list_enable_thumbs (GTH_FILE_LIST (list_view), TRUE);
+	gth_file_list_set_caption (GTH_FILE_LIST (list_view), "none");
+	gth_file_list_set_sort_func (GTH_FILE_LIST (list_view), gth_main_get_sort_type ("file::name")->cmp_func, FALSE);
+	gtk_widget_show (list_view);
+	gtk_box_pack_start (GTK_BOX (GET_WIDGET ("images_box")), list_view, TRUE, TRUE, 0);
+	gth_file_list_set_files (GTH_FILE_LIST (list_view), data->file_list);
+
+	gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (GET_WIDGET ("album_comboboxentry")))), g_file_info_get_edit_name (data->location->info));
+	gtk_widget_set_sensitive (GET_WIDGET ("upload_button"), FALSE);
+
+	title = g_strdup_printf (_("Export to %s"), data->server->name);
+	gtk_window_set_title (GTK_WINDOW (data->dialog), title);
+	g_free (title);
+
+	/* Set the signals handlers. */
+
+	g_signal_connect (G_OBJECT (data->dialog),
+			  "destroy",
+			  G_CALLBACK (export_dialog_destroy_cb),
+			  data);
+	g_signal_connect (data->dialog,
+			  "response",
+			  G_CALLBACK (export_dialog_response_cb),
+			  data);
+	g_signal_connect (GET_WIDGET ("edit_accounts_button"),
+			  "clicked",
+			  G_CALLBACK (edit_accounts_button_clicked_cb),
+			  data);
+	g_signal_connect (GET_WIDGET ("account_combobox"),
+			  "changed",
+			  G_CALLBACK (account_combobox_changed_cb),
+			  data);
+
+	data->conn = oauth_connection_new (photobucket_server);
+	data->service = photobucket_service_new (data->conn);
+	data->auth = oauth_authentication_new (data->conn,
+					       data->cancellable,
+					       GTK_WIDGET (data->browser),
+					       data->dialog);
+	g_signal_connect (data->auth,
+			  "ready",
+			  G_CALLBACK (authentication_ready_cb),
+			  data);
+	g_signal_connect (data->auth,
+			  "accounts_changed",
+			  G_CALLBACK (authentication_accounts_changed_cb),
+			  data);
+
+	data->progress_dialog = gth_progress_dialog_new (GTK_WINDOW (data->browser));
+	gth_progress_dialog_add_task (GTH_PROGRESS_DIALOG (data->progress_dialog), GTH_TASK (data->conn));
+
+	oauth_authentication_auto_connect (data->auth);
+}
diff --git a/extensions/photobucket/dlg-export-to-photobucket.h b/extensions/photobucket/dlg-export-to-photobucket.h
new file mode 100644
index 0000000..0dd649e
--- /dev/null
+++ b/extensions/photobucket/dlg-export-to-photobucket.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 The Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DLG_EXPORT_TO_PHOTOBUCKET_H
+#define DLG_EXPORT_TO_PHOTOBUCKET_H
+
+#include <gthumb.h>
+
+void dlg_export_to_photobucket (GthBrowser *browser,
+				GList      *file_list);
+
+#endif /* DLG_EXPORT_TO_PHOTOBUCKET_H */
diff --git a/extensions/photobucket/main.c b/extensions/photobucket/main.c
new file mode 100644
index 0000000..b9c8e5a
--- /dev/null
+++ b/extensions/photobucket/main.c
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "callbacks.h"
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+	gth_hook_add_callback ("gth-browser-construct", 10, G_CALLBACK (pb__gth_browser_construct_cb), NULL);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+	return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/photobucket/photobucket-service.c b/extensions/photobucket/photobucket-service.c
new file mode 100644
index 0000000..82680fe
--- /dev/null
+++ b/extensions/photobucket/photobucket-service.c
@@ -0,0 +1,375 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gthumb.h>
+#include "photobucket-service.h"
+
+
+struct _PhotobucketServicePrivate
+{
+	PhotobucketConnection *conn;
+	PhotobucketUser       *user;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+photobucket_service_finalize (GObject *object)
+{
+	PhotobucketService *self;
+
+	self = PHOTOBUCKET_SERVICE (object);
+
+	_g_object_unref (self->priv->conn);
+	_g_object_unref (self->priv->user);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+photobucket_service_class_init (PhotobucketServiceClass *klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (PhotobucketServicePrivate));
+
+	object_class = (GObjectClass*) klass;
+	object_class->finalize = photobucket_service_finalize;
+}
+
+
+static void
+photobucket_service_init (PhotobucketService *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, PHOTOBUCKET_TYPE_SERVICE, PhotobucketServicePrivate);
+	self->priv->conn = NULL;
+	self->priv->user = NULL;
+}
+
+
+GType
+photobucket_service_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo type_info = {
+			sizeof (PhotobucketServiceClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) photobucket_service_class_init,
+			NULL,
+			NULL,
+			sizeof (PhotobucketService),
+			0,
+			(GInstanceInitFunc) photobucket_service_init
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT,
+					       "PhotobucketService",
+					       &type_info,
+					       0);
+	}
+
+	return type;
+}
+
+
+PhotobucketService *
+photobucket_service_new (PhotobucketConnection *conn)
+{
+	PhotobucketService *self;
+
+	self = (PhotobucketService *) g_object_new (PHOTOBUCKET_TYPE_SERVICE, NULL);
+	self->priv->conn = g_object_ref (conn);
+
+	return self;
+}
+
+
+/* -- photobucket_service_login_request -- */
+
+
+static void
+login_request_ready_cb (SoupSession *session,
+			SoupMessage *msg,
+			gpointer     user_data)
+{
+	PhotobucketService *self = user_data;
+	GSimpleAsyncResult *result;
+	SoupBuffer         *body;
+	DomDocument        *doc = NULL;
+	GError             *error = NULL;
+
+	result = oauth_connection_get_result (self->priv->conn);
+
+	if (msg->status_code != 200) {
+		g_simple_async_result_set_error (result,
+						 SOUP_HTTP_ERROR,
+						 msg->status_code,
+						 "%s",
+						 soup_status_get_phrase (msg->status_code));
+		g_simple_async_result_complete_in_idle (result);
+		return;
+	}
+
+	body = soup_message_body_flatten (msg->response_body);
+	if (photobucket_utils_parse_response (body, &doc, &error)) {
+		DomElement *root;
+		char       *uid = NULL;
+
+		root = DOM_ELEMENT (doc)->first_child;
+		if (g_strcmp0 (root->tag_name, "users_getLoggedInUser_response") == 0)
+			uid = g_strdup (dom_element_get_inner_text (root));
+
+		if (uid == NULL) {
+			error = g_error_new_literal (PHOTOBUCKET_CONNECTION_ERROR, 0, _("Unknown error"));
+			g_simple_async_result_set_from_error (result, error);
+		}
+		else
+			g_simple_async_result_set_op_res_gboolean (result, TRUE);
+
+		g_object_unref (doc);
+	}
+	else
+		g_simple_async_result_set_from_error (result, error);
+
+	g_simple_async_result_complete_in_idle (result);
+
+	soup_buffer_free (body);
+}
+
+
+void
+photobucket_service_login_request (PhotobucketService  *self,
+				   GCancellable        *cancellable,
+				   GAsyncReadyCallback  callback,
+				   gpointer             user_data)
+{
+	GHashTable  *data_set;
+	SoupMessage *msg;
+
+	gth_task_progress (GTH_TASK (self->priv->conn), _("Connecting to the server"), _("Getting account information"), TRUE, 0.0);
+
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+	uri = "http://api.photobucket.com/login/request";;
+	oauth_connection_add_signature (self->priv->conn, "POST", uri, data_set);
+	msg = soup_form_request_new_from_hash ("POST", uri, data_set);
+	oauth_connection_send_message (self->priv->conn,
+				       msg,
+				       cancellable,
+				       callback,
+				       user_data,
+				       photobucket_service_login_request,
+				       login_request_ready_cb,
+				       self);
+
+	g_hash_table_destroy (data_set);
+}
+
+
+gboolean
+photobucket_service_login_request_finish (PhotobucketService  *self,
+					    GAsyncResult     *result,
+					    GError          **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return FALSE;
+	else
+		return TRUE;
+}
+
+
+char *
+photobucket_service_get_login_link (PhotobucketService *self)
+{
+
+}
+
+
+/* utilities */
+
+
+gboolean
+photobucket_utils_parse_response (SoupBuffer   *body,
+				  DomDocument **doc_p,
+				  GError      **error)
+{
+	DomDocument *doc;
+	DomElement  *node;
+
+	doc = dom_document_new ();
+	if (! dom_document_load (doc, body->data, body->length, error)) {
+		g_object_unref (doc);
+		return FALSE;
+	}
+
+	for (node = DOM_ELEMENT (doc)->first_child; node; node = node->next_sibling) {
+		if (g_strcmp0 (node->tag_name, "response") == 0) {
+			DomElement *child;
+			const char *status = NULL;
+			const char *message;
+			const char *code;
+
+			for (child = node->first_child; child; child = child->next_sibling) {
+				if (g_strcmp0 (child->tag_name, "status") == 0) {
+					status = dom_element_get_inner_text (child);
+				}
+				else if (g_strcmp0 (child->tag_name, "message") == 0) {
+					message = dom_element_get_inner_text (child);
+				}
+				else if (g_strcmp0 (child->tag_name, "code") == 0) {
+					code = dom_element_get_inner_text (child);
+				}
+			}
+
+			if (status == NULL) {
+				error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 999, _("Unknown error"));
+				g_simple_async_result_set_from_error (self->priv->result, error);
+			}
+			else if (strcmp (status, "Exception") == 0) {
+				*error = g_error_new_literal (OAUTH_CONNECTION_ERROR,
+							      (code != NULL) ? atoi (code) : 999,
+							      (message != NULL) ? message : _("Unknown error"));
+			}
+
+			g_object_unref (doc);
+			return FALSE;
+		}
+	}
+
+	*doc_p = doc;
+
+	return TRUE;
+}
+
+
+GList *
+photobucket_accounts_load_from_file (void)
+{
+	GList       *accounts = NULL;
+	char        *filename;
+	char        *buffer;
+	gsize        len;
+	DomDocument *doc;
+
+	filename = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, "accounts", "photobucket.xml", NULL);
+	if (! g_file_get_contents (filename, &buffer, &len, NULL)) {
+		g_free (filename);
+		return NULL;
+	}
+
+	doc = dom_document_new ();
+	if (dom_document_load (doc, buffer, len, NULL)) {
+		DomElement *node;
+
+		node = DOM_ELEMENT (doc)->first_child;
+		if ((node != NULL) && (g_strcmp0 (node->tag_name, "accounts") == 0)) {
+			DomElement *child;
+
+			for (child = node->first_child;
+			     child != NULL;
+			     child = child->next_sibling)
+			{
+				if (strcmp (child->tag_name, "account") == 0) {
+					PhotobucketAccount *account;
+
+					account = photobucket_account_new ();
+					dom_domizable_load_from_element (DOM_DOMIZABLE (account), child);
+
+					accounts = g_list_prepend (accounts, account);
+				}
+			}
+
+			accounts = g_list_reverse (accounts);
+		}
+	}
+
+	g_object_unref (doc);
+	g_free (buffer);
+	g_free (filename);
+
+	return accounts;
+}
+
+
+PhotobucketAccount *
+photobucket_accounts_find_default (GList *accounts)
+{
+	GList *scan;
+
+	for (scan = accounts; scan; scan = scan->next) {
+		PhotobucketAccount *account = scan->data;
+
+		if (account->is_default)
+			return g_object_ref (account);
+	}
+
+	return NULL;
+}
+
+
+void
+photobucket_accounts_save_to_file (GList         *accounts,
+			      PhotobucketAccount *default_account)
+{
+	DomDocument *doc;
+	DomElement  *root;
+	GList       *scan;
+	char        *buffer;
+	gsize        len;
+	char        *filename;
+	GFile       *file;
+
+	doc = dom_document_new ();
+	root = dom_document_create_element (doc, "accounts", NULL);
+	dom_element_append_child (DOM_ELEMENT (doc), root);
+	for (scan = accounts; scan; scan = scan->next) {
+		PhotobucketAccount *account = scan->data;
+		DomElement    *node;
+
+		if ((default_account != NULL) && g_strcmp0 (account->username, default_account->username) == 0)
+			account->is_default = TRUE;
+		else
+			account->is_default = FALSE;
+		node = dom_domizable_create_element (DOM_DOMIZABLE (account), doc);
+		dom_element_append_child (root, node);
+	}
+
+	gth_user_dir_make_dir_for_file (GTH_DIR_CONFIG, GTHUMB_DIR, "accounts", "photobucket.xml", NULL);
+	filename = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, "accounts", "photobucket.xml", NULL);
+	file = g_file_new_for_path (filename);
+	buffer = dom_document_dump (doc, &len);
+	g_write_file (file, FALSE, G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION, buffer, len, NULL, NULL);
+
+	g_free (buffer);
+	g_object_unref (file);
+	g_free (filename);
+	g_object_unref (doc);
+}
diff --git a/extensions/photobucket/photobucket-service.h b/extensions/photobucket/photobucket-service.h
new file mode 100644
index 0000000..11aedf3
--- /dev/null
+++ b/extensions/photobucket/photobucket-service.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2010 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef PHOTOBUCKET_SERVICE_H
+#define PHOTOBUCKET_SERVICE_H
+
+#include <glib-object.h>
+#include <extensions/oauth/oauth-connection.h>
+
+#define PHOTOBUCKET_TYPE_SERVICE         (photobucket_service_get_type ())
+#define PHOTOBUCKET_SERVICE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), PHOTOBUCKET_TYPE_SERVICE, PhotobucketService))
+#define PHOTOBUCKET_SERVICE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), PHOTOBUCKET_TYPE_SERVICE, PhotobucketServiceClass))
+#define PHOTOBUCKET_IS_SERVICE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), PHOTOBUCKET_TYPE_SERVICE))
+#define PHOTOBUCKET_IS_SERVICE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), PHOTOBUCKET_TYPE_SERVICE))
+#define PHOTOBUCKET_SERVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), PHOTOBUCKET_TYPE_SERVICE, PhotobucketServiceClass))
+
+typedef struct _PhotobucketService         PhotobucketService;
+typedef struct _PhotobucketServicePrivate  PhotobucketServicePrivate;
+typedef struct _PhotobucketServiceClass    PhotobucketServiceClass;
+
+struct _PhotobucketService
+{
+	GObject __parent;
+	PhotobucketServicePrivate *priv;
+};
+
+struct _PhotobucketServiceClass
+{
+	GObjectClass __parent_class;
+};
+
+GType                 photobucket_service_get_type                 (void) G_GNUC_CONST;
+PhotobucketService *  photobucket_service_new                      (OAuthConnection      *conn);
+void                  photobucket_service_login_request            (PhotobucketService   *self,
+								    GCancellable         *cancellable,
+								    GAsyncReadyCallback   callback,
+								    gpointer              user_data);
+gboolean              photobucket_service_login_request_finish     (PhotobucketService   *self,
+						                    GAsyncResult         *result,
+						                    GError              **error);
+char *                photobucket_service_get_login_link           (PhotobucketService   *self);
+
+/* utilities */
+
+gboolean              photobucket_utils_parse_response     (SoupBuffer         *body,
+							    DomDocument       **doc_p,
+							    GError            **error);
+GList *               photobucket_accounts_load_from_file  (void);
+PhotobucketAccount *  photobucket_accounts_find_default    (GList              *accounts);
+void                  photobucket_accounts_save_to_file    (GList              *accounts,
+							    PhotobucketAccount *default_account);
+
+#endif /* PHOTOBUCKET_SERVICE_H */
diff --git a/extensions/photobucket/photobucket.extension.in.in b/extensions/photobucket/photobucket.extension.in.in
new file mode 100644
index 0000000..9d3ae74
--- /dev/null
+++ b/extensions/photobucket/photobucket.extension.in.in
@@ -0,0 +1,13 @@
+[Extension]
+_Name=PhotoBucket
+_Description=Upload images to PhotoBucket
+Authors=gthumb development team
+Copyright=Copyright © 2010 The Free Software Foundation, Inc.
+Version=1.0
+Icon=photobucket
+Category=Exporter
+
+[Loader]
+Type=module
+File=%LIBRARY%
+Requires=oauth



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