[gthumb] [photobucket] big batch of changes



commit 719df0cc80e2e7d7a4baade43a75c93e5bb7ae8a
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Tue May 18 21:02:51 2010 +0200

    [photobucket] big batch of changes
    
    Completed the authentication/authorization process; Started work
    on the export dialog.

 data/icons/hicolor/16x16/actions/Makefile.am       |    1 +
 data/icons/hicolor/16x16/actions/photobucket.png   |  Bin 0 -> 703 bytes
 extensions/oauth/oauth-account.c                   |    6 +-
 extensions/oauth/oauth-account.h                   |   25 +-
 extensions/oauth/oauth-authentication.c            |   37 +-
 extensions/oauth/oauth-authentication.h            |    4 +-
 extensions/oauth/oauth-connection.c                |   44 +-
 extensions/oauth/oauth-connection.h                |   50 +-
 extensions/photobucket/Makefile.am                 |   12 +-
 extensions/photobucket/callbacks.c                 |    6 +-
 extensions/photobucket/data/ui/Makefile.am         |    3 +-
 .../photobucket/data/ui/export-to-photobucket.ui   |  225 ++----
 .../data/ui/photobucket-album-properties.ui        |  163 ++++
 extensions/photobucket/dlg-export-to-photobucket.c |  366 ++++----
 extensions/photobucket/photobucket-account.c       |  209 +++++
 extensions/photobucket/photobucket-account.h       |   74 ++
 .../photobucket-album-properties-dialog.c          |  142 +++
 .../photobucket-album-properties-dialog.h          |   58 ++
 extensions/photobucket/photobucket-album.c         |  149 +++
 extensions/photobucket/photobucket-album.h         |   60 ++
 extensions/photobucket/photobucket-consumer.c      |  155 ++--
 extensions/photobucket/photobucket-consumer.h      |    6 +
 extensions/photobucket/photobucket-photo.c         |  231 +++++
 extensions/photobucket/photobucket-photo.h         |   81 ++
 extensions/photobucket/photobucket-service.c       |  989 ++++++++++++++++++++
 extensions/photobucket/photobucket-service.h       |  107 +++
 gthumb/gsignature.c                                |   29 +-
 gthumb/gsignature.h                                |    6 +
 tests/Makefile.am                                  |    6 +-
 tests/gsignature-test.c                            |   38 +-
 tests/oauth-test.c                                 |  133 +++
 31 files changed, 2931 insertions(+), 484 deletions(-)
---
diff --git a/data/icons/hicolor/16x16/actions/Makefile.am b/data/icons/hicolor/16x16/actions/Makefile.am
index e435660..286c79c 100644
--- a/data/icons/hicolor/16x16/actions/Makefile.am
+++ b/data/icons/hicolor/16x16/actions/Makefile.am
@@ -12,6 +12,7 @@ icons_DATA = 			\
 	file-search.png		\
 	flickr.png		\
 	histogram.png		\
+	photobucket.png		\
 	picasaweb.png		\
 	tool-adjust-colors.png	\
 	tool-crop.png		\
diff --git a/data/icons/hicolor/16x16/actions/photobucket.png b/data/icons/hicolor/16x16/actions/photobucket.png
new file mode 100644
index 0000000..ea19560
Binary files /dev/null and b/data/icons/hicolor/16x16/actions/photobucket.png differ
diff --git a/extensions/oauth/oauth-account.c b/extensions/oauth/oauth-account.c
index 905f907..90a449f 100644
--- a/extensions/oauth/oauth-account.c
+++ b/extensions/oauth/oauth-account.c
@@ -56,7 +56,7 @@ oauth_account_class_init (OAuthAccountClass *klass)
 }
 
 
-static DomElement*
+DomElement*
 oauth_account_create_element (DomDomizable *base,
 			      DomDocument  *doc)
 {
@@ -88,9 +88,9 @@ oauth_account_create_element (DomDomizable *base,
 }
 
 
-static void
+void
 oauth_account_load_from_element (DomDomizable *base,
-			          DomElement   *element)
+			         DomElement   *element)
 {
 	OAuthAccount *self;
 
diff --git a/extensions/oauth/oauth-account.h b/extensions/oauth/oauth-account.h
index 1592d15..3fb6b36 100644
--- a/extensions/oauth/oauth-account.h
+++ b/extensions/oauth/oauth-account.h
@@ -25,6 +25,7 @@
 
 #include <glib.h>
 #include <glib-object.h>
+#include <gthumb.h>
 
 G_BEGIN_DECLS
 
@@ -53,16 +54,20 @@ 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_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);
+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_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);
+DomElement *      oauth_account_create_element     (DomDomizable *base,
+					            DomDocument  *doc);
+void              oauth_account_load_from_element  (DomDomizable *base,
+						    DomElement   *element);
 
 G_END_DECLS
 
diff --git a/extensions/oauth/oauth-authentication.c b/extensions/oauth/oauth-authentication.c
index 5326821..7e1ab99 100644
--- a/extensions/oauth/oauth-authentication.c
+++ b/extensions/oauth/oauth-authentication.c
@@ -146,6 +146,7 @@ oauth_authentication_get_type (void)
 
 OAuthAuthentication *
 oauth_authentication_new (OAuthConnection *conn,
+			  GType            account_type,
 			  GCancellable    *cancellable,
 			  GtkWidget       *browser,
 			  GtkWidget       *dialog)
@@ -157,7 +158,7 @@ oauth_authentication_new (OAuthConnection *conn,
 	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->conn->consumer->name);
+	self->priv->accounts = oauth_accounts_load_from_file (self->priv->conn->consumer->name, account_type);
 	self->priv->account = oauth_accounts_find_default (self->priv->accounts);
 	self->priv->browser = browser;
 	self->priv->dialog = dialog;
@@ -244,7 +245,9 @@ check_token_ready_cb (GObject      *source_object,
 		show_authentication_error_dialog (self, &error);
 		return;
 	}
-	oauth_accounts_save_to_file (self->priv->conn->consumer->name, self->priv->accounts, self->priv->account);
+	oauth_accounts_save_to_file (self->priv->conn->consumer->name,
+				     self->priv->accounts,
+				     self->priv->account);
 	g_signal_emit (self, oauth_authentication_signals[READY], 0);
 }
 
@@ -261,6 +264,7 @@ connect_to_server_step2 (OAuthAuthentication *self)
 				    self->priv->account->token,
 				    self->priv->account->token_secret);
 	oauth_connection_check_token (self->priv->conn,
+				      self->priv->account,
 				      self->priv->cancellable,
 				      check_token_ready_cb,
 				      self);
@@ -357,7 +361,7 @@ get_access_token_ready_cb (GObject      *source_object,
 	GError              *error = NULL;
 	OAuthAccount        *account;
 
-	account = oauth_connection_get_access_token_finish (OAUTH_CONNECTION (source_object), res, &error);
+	account = oauth_connection_get_access_token_finish (self->priv->conn, res, &error);
 	if (error != NULL) {
 		show_authentication_error_dialog (self, &error);
 		return;
@@ -541,7 +545,7 @@ get_request_token_ready_cb (GObject      *source_object,
 	OAuthAuthentication *self = user_data;
 	GError              *error = NULL;
 
-	if (! oauth_connection_get_request_token_finish (OAUTH_CONNECTION (source_object), res, &error))
+	if (! oauth_connection_get_request_token_finish (self->priv->conn, res, &error))
 		show_authentication_error_dialog (self, &error);
 	else
 		ask_authorization (self);
@@ -720,16 +724,23 @@ oauth_authentication_edit_accounts (OAuthAuthentication *self,
 
 
 GList *
-oauth_accounts_load_from_file (const char *service_name)
+oauth_accounts_load_from_file (const char *service_name,
+			       GType       account_type)
 {
 	GList       *accounts = NULL;
 	char        *filename;
+	char        *path;
 	char        *buffer;
 	gsize        len;
+	GError      *error = NULL;
 	DomDocument *doc;
 
-	filename = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, "accounts", service_name, ".xml", NULL);
-	if (! g_file_get_contents (filename, &buffer, &len, NULL)) {
+	filename = g_strconcat (service_name, ".xml", NULL);
+	path = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, "accounts", filename, NULL);
+	if (! g_file_get_contents (path, &buffer, &len, &error)) {
+		g_warning ("%s\n", error->message);
+		g_error_free (error);
+		g_free (path);
 		g_free (filename);
 		return NULL;
 	}
@@ -749,7 +760,7 @@ oauth_accounts_load_from_file (const char *service_name)
 				if (strcmp (child->tag_name, "account") == 0) {
 					OAuthAccount *account;
 
-					account = oauth_account_new ();
+					account = g_object_new (account_type, NULL);
 					dom_domizable_load_from_element (DOM_DOMIZABLE (account), child);
 
 					accounts = g_list_prepend (accounts, account);
@@ -762,6 +773,7 @@ oauth_accounts_load_from_file (const char *service_name)
 
 	g_object_unref (doc);
 	g_free (buffer);
+	g_free (path);
 	g_free (filename);
 
 	return accounts;
@@ -795,6 +807,7 @@ oauth_accounts_save_to_file (const char   *service_name,
 	char        *buffer;
 	gsize        len;
 	char        *filename;
+	char        *path;
 	GFile       *file;
 
 	doc = dom_document_new ();
@@ -812,14 +825,16 @@ oauth_accounts_save_to_file (const char   *service_name,
 		dom_element_append_child (root, node);
 	}
 
-	gth_user_dir_make_dir_for_file (GTH_DIR_CONFIG, GTHUMB_DIR, "accounts", service_name, ".xml", NULL);
-	filename = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, "accounts", service_name, ".xml", NULL);
-	file = g_file_new_for_path (filename);
+	filename = g_strconcat (service_name, ".xml", NULL);
+	gth_user_dir_make_dir_for_file (GTH_DIR_CONFIG, GTHUMB_DIR, "accounts", filename, NULL);
+	path = gth_user_dir_get_file (GTH_DIR_CONFIG, GTHUMB_DIR, "accounts", filename, NULL);
+	file = g_file_new_for_path (path);
 	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 (path);
 	g_free (filename);
 	g_object_unref (doc);
 }
diff --git a/extensions/oauth/oauth-authentication.h b/extensions/oauth/oauth-authentication.h
index ee1ddab..69c6d2f 100644
--- a/extensions/oauth/oauth-authentication.h
+++ b/extensions/oauth/oauth-authentication.h
@@ -57,6 +57,7 @@ struct _OAuthAuthenticationClass {
 
 GType                   oauth_authentication_get_type       (void);
 OAuthAuthentication *   oauth_authentication_new            (OAuthConnection     *conn,
+							     GType                account_type,
 							     GCancellable        *cancellable,
 							     GtkWidget           *browser,
 							     GtkWidget           *dialog);
@@ -70,7 +71,8 @@ void                    oauth_authentication_edit_accounts  (OAuthAuthentication
 
 /* utilities */
 
-GList *          oauth_accounts_load_from_file  (const char    *service_name);
+GList *          oauth_accounts_load_from_file  (const char    *service_name,
+						 GType          account_type);
 OAuthAccount *   oauth_accounts_find_default    (GList         *accounts);
 void             oauth_accounts_save_to_file    (const char    *service_name,
 						 GList         *accounts,
diff --git a/extensions/oauth/oauth-connection.c b/extensions/oauth/oauth-connection.c
index 4f27afd..996a581 100644
--- a/extensions/oauth/oauth-connection.c
+++ b/extensions/oauth/oauth-connection.c
@@ -203,7 +203,7 @@ oauth_connection_send_message (OAuthConnection     *self,
 	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),
+	self->priv->result = g_simple_async_result_new (G_OBJECT (self),
 							callback,
 							user_data,
 							source_tag);
@@ -317,12 +317,13 @@ oauth_connection_add_signature (OAuthConnection *self,
 	/* Calculate the signature value */
 
 	signature_key = g_string_new ("");
-	g_string_append (signature_key, self->consumer->consumer_key);
+	g_string_append (signature_key, self->consumer->consumer_secret);
 	g_string_append (signature_key, "&");
-	if (self->priv->token != NULL)
-		g_string_append (signature_key, self->priv->token);
+	if (self->priv->token_secret != NULL)
+		g_string_append (signature_key, self->priv->token_secret);
 	g_free (self->priv->signature);
 	self->priv->signature = g_compute_signature_for_string (G_CHECKSUM_SHA1,
+								G_SIGNATURE_ENC_BASE64,
 							        signature_key->str,
 							        signature_key->len,
 							        base_string->str,
@@ -506,12 +507,19 @@ oauth_connection_get_token_secret (OAuthConnection *self)
 /* -- oauth_connection_check_token -- */
 
 
+typedef struct {
+	OAuthConnection *conn;
+	OAuthAccount    *account;
+} CheckTokenData;
+
+
 static void
 check_token_ready_cb (SoupSession *session,
 		      SoupMessage *msg,
 		      gpointer     user_data)
 {
-	OAuthConnection *self = user_data;
+	CheckTokenData  *check_token_data = user_data;
+	OAuthConnection *self = check_token_data->conn;
 	SoupBuffer      *body;
 
 	if (msg->status_code != 200) {
@@ -525,27 +533,40 @@ check_token_ready_cb (SoupSession *session,
 	}
 
 	body = soup_message_body_flatten (msg->response_body);
-	self->consumer->check_token_response (self, msg, body, self->priv->result);
+	self->consumer->check_token_response (self, msg, body, self->priv->result, check_token_data->account);
 	g_simple_async_result_complete_in_idle (self->priv->result);
 
 	soup_buffer_free (body);
+	g_free (check_token_data);
 }
 
 
 void
 oauth_connection_check_token (OAuthConnection     *self,
+			      OAuthAccount        *account,
 			      GCancellable        *cancellable,
 			      GAsyncReadyCallback  callback,
 			      gpointer             user_data)
 {
-	GHashTable  *data_set;
-	SoupMessage *msg;
+	CheckTokenData *check_token_data;
+	GHashTable     *data_set;
+	char           *url;
+	SoupMessage    *msg;
+
+	check_token_data = g_new0 (CheckTokenData, 1);
+	check_token_data->conn = self;
+	check_token_data->account = account;
 
 	gth_task_progress (GTH_TASK (self), _("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, "POST", self->consumer->check_token_url, data_set);
-	msg = soup_form_request_new_from_hash ("POST", self->consumer->check_token_url, data_set);
+
+	url = self->consumer->get_check_token_url (self, account, TRUE);
+	oauth_connection_add_signature (self, "GET", url, data_set);
+	g_free (url);
+
+	url = self->consumer->get_check_token_url (self, account, FALSE);
+	msg = soup_form_request_new_from_hash ("GET", url, data_set);
 	oauth_connection_send_message (self,
 				       msg,
 				       cancellable,
@@ -553,8 +574,9 @@ oauth_connection_check_token (OAuthConnection     *self,
 				       user_data,
 				       oauth_connection_check_token,
 				       check_token_ready_cb,
-				       self);
+				       check_token_data);
 
+	g_free (url);
 	g_hash_table_destroy (data_set);
 }
 
diff --git a/extensions/oauth/oauth-connection.h b/extensions/oauth/oauth-connection.h
index b684a58..5b5ab6b 100644
--- a/extensions/oauth/oauth-connection.h
+++ b/extensions/oauth/oauth-connection.h
@@ -98,6 +98,7 @@ void                 oauth_connection_set_token                (OAuthConnection
 const char *         oauth_connection_get_token                (OAuthConnection      *self);
 const char *         oauth_connection_get_token_secret         (OAuthConnection      *self);
 void                 oauth_connection_check_token              (OAuthConnection      *self,
+								OAuthAccount         *account,
 							        GCancellable         *cancellable,
 							        GAsyncReadyCallback   callback,
 							        gpointer              user_data);
@@ -107,31 +108,34 @@ gboolean             oauth_connection_check_token_finish       (OAuthConnection
 
 /* -- OAuthConsumer -- */
 
-typedef void   (*OAuthResponseFunc)  (OAuthConnection    *self,
-				      SoupMessage        *msg,
-				      SoupBuffer         *body,
-				      GSimpleAsyncResult *result);
-typedef char * (*OAuthLoginLinkFunc) (OAuthConnection    *self);
-
+typedef void   (*OAuthResponseFunc)        (OAuthConnection    *self,
+				            SoupMessage        *msg,
+				            SoupBuffer         *body,
+				            GSimpleAsyncResult *result);
+typedef void   (*OAuthAccountResponseFunc) (OAuthConnection    *self,
+				            SoupMessage        *msg,
+				            SoupBuffer         *body,
+				            GSimpleAsyncResult *result,
+				            OAuthAccount       *account);
+typedef char * (*OAuthStringFunc)          (OAuthConnection    *self);
+typedef char * (*OAuthUrlFunc)             (OAuthConnection    *self,
+				            OAuthAccount       *account,
+				            gboolean            for_signature);
 
 struct _OAuthConsumer {
-	const char         *display_name;
-	const char         *name;
-	const char         *url;
-	const char         *protocol;
-	const char         *consumer_key;
-	const char         *consumer_secret;
-
-	const char         *request_token_url;
-	OAuthResponseFunc   get_request_token_response;
-
-	OAuthLoginLinkFunc  get_login_link;
-
-	const char         *access_token_url;
-	OAuthResponseFunc   get_access_token_response;
-
-	const char         *check_token_url;
-	OAuthResponseFunc   check_token_response;
+	const char               *display_name;
+	const char               *name;
+	const char               *url;
+	const char               *protocol;
+	const char               *consumer_key;
+	const char               *consumer_secret;
+	const char               *request_token_url;
+	OAuthResponseFunc         get_request_token_response;
+	OAuthStringFunc           get_login_link;
+	const char               *access_token_url;
+	OAuthResponseFunc         get_access_token_response;
+	OAuthUrlFunc              get_check_token_url;
+	OAuthAccountResponseFunc  check_token_response;
 };
 
 #endif /* OAUTH_CONNECTION_H */
diff --git a/extensions/photobucket/Makefile.am b/extensions/photobucket/Makefile.am
index 616c74b..638896f 100644
--- a/extensions/photobucket/Makefile.am
+++ b/extensions/photobucket/Makefile.am
@@ -13,8 +13,18 @@ libphotobucket_la_SOURCES = 			\
 	dlg-export-to-photobucket.c		\
 	dlg-export-to-photobucket.h		\
 	main.c					\
+	photobucket-account.c			\
+	photobucket-account.h			\
+	photobucket-album.c			\
+	photobucket-album.h			\
+	photobucket-album-properties-dialog.c	\
+	photobucket-album-properties-dialog.h	\
 	photobucket-consumer.c			\
-	photobucket-consumer.h
+	photobucket-consumer.h			\
+	photobucket-photo.c			\
+	photobucket-photo.h			\
+	photobucket-service.c			\
+	photobucket-service.h
 
 libphotobucket_la_CFLAGS = $(GTHUMB_CFLAGS) $(LIBSOUP_CFLAGS) $(GNOME_KEYRING_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
 libphotobucket_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
diff --git a/extensions/photobucket/callbacks.c b/extensions/photobucket/callbacks.c
index 1180c90..a53d7d6 100644
--- a/extensions/photobucket/callbacks.c
+++ b/extensions/photobucket/callbacks.c
@@ -47,8 +47,8 @@ static const char *ui_info =
 
 static GtkActionEntry action_entries[] = {
 	{ "File_Export_PhotoBucket", "photobucket",
-	  N_("PhotoBucket..."), NULL,
-	  N_("Upload photos to PhotoBucket"),
+	  N_("Photobucket..."), NULL,
+	  N_("Upload photos to Photobucket"),
 	  G_CALLBACK (gth_browser_activate_action_export_photobucket) },
 };
 
@@ -66,7 +66,7 @@ browser_data_free (BrowserData *data)
 
 
 void
-fl__gth_browser_construct_cb (GthBrowser *browser)
+pb__gth_browser_construct_cb (GthBrowser *browser)
 {
 	BrowserData *data;
 	GError      *error = NULL;
diff --git a/extensions/photobucket/data/ui/Makefile.am b/extensions/photobucket/data/ui/Makefile.am
index e95fff4..89536cc 100644
--- a/extensions/photobucket/data/ui/Makefile.am
+++ b/extensions/photobucket/data/ui/Makefile.am
@@ -1,7 +1,8 @@
 uidir = $(pkgdatadir)/ui
 ui_DATA = 					\
 	export-to-photobucket.ui		\
-	photobucket-export-completed.ui		
+	photobucket-album-properties.ui		\
+	photobucket-export-completed.ui
 
 EXTRA_DIST = $(ui_DATA)
 
diff --git a/extensions/photobucket/data/ui/export-to-photobucket.ui b/extensions/photobucket/data/ui/export-to-photobucket.ui
index e9e12e5..8010dd9 100644
--- a/extensions/photobucket/data/ui/export-to-photobucket.ui
+++ b/extensions/photobucket/data/ui/export-to-photobucket.ui
@@ -2,8 +2,33 @@
 <interface>
   <requires lib="gtk+" version="2.16"/>
   <!-- interface-naming-policy project-wide -->
+  <object class="GtkListStore" id="album_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>
+  <object class="GtkListStore" id="account_liststore">
+    <columns>
+      <!-- column-name account -->
+      <column type="GObject"/>
+      <!-- column-name username -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkImage" id="upload_image">
+    <property name="visible">True</property>
+    <property name="stock">gtk-goto-top</property>
+  </object>
   <object class="GtkDialog" id="export_dialog">
     <property name="border_width">5</property>
+    <property name="title" translatable="yes">Export to Flickr</property>
     <property name="type_hint">dialog</property>
     <property name="has_separator">False</property>
     <child internal-child="vbox">
@@ -43,7 +68,7 @@
                 <child>
                   <object class="GtkTable" id="table2">
                     <property name="visible">True</property>
-                    <property name="n_rows">5</property>
+                    <property name="n_rows">2</property>
                     <property name="n_columns">2</property>
                     <property name="column_spacing">6</property>
                     <property name="row_spacing">5</property>
@@ -118,7 +143,7 @@
                             <property name="visible">True</property>
                             <property name="spacing">6</property>
                             <child>
-                              <object class="GtkLabel" id="label4">
+                              <object class="GtkLabel" id="label2">
                                 <property name="visible">True</property>
                                 <property name="label" translatable="yes">Free space:</property>
                                 <attributes>
@@ -158,114 +183,54 @@
                       <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="label" translatable="yes">A_lbum:</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">
+                      <object class="GtkHBox" id="hbox2">
                         <property name="visible">True</property>
-                        <property name="model">privacy_liststore</property>
-                        <property name="active">0</property>
+                        <property name="spacing">6</property>
                         <child>
-                          <object class="GtkCellRendererText" id="cellrenderertext5"/>
-                          <attributes>
-                            <attribute name="text">1</attribute>
-                          </attributes>
+                          <object class="GtkComboBox" id="album_combobox">
+                            <property name="visible">True</property>
+                            <property name="model">album_liststore</property>
+                          </object>
+                          <packing>
+                            <property name="position">0</property>
+                          </packing>
                         </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>
+                          <object class="GtkButton" id="add_album_button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="tooltip_text" translatable="yes">Add a new album</property>
+                            <property name="use_action_appearance">False</property>
+                            <child>
+                              <object class="GtkImage" id="image1">
+                                <property name="visible">True</property>
+                                <property name="stock">gtk-add</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
                         </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>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
                       </packing>
                     </child>
                   </object>
@@ -348,80 +313,4 @@
       <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-album-properties.ui b/extensions/photobucket/data/ui/photobucket-album-properties.ui
new file mode 100644
index 0000000..410c2ac
--- /dev/null
+++ b/extensions/photobucket/data/ui/photobucket-album-properties.ui
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.18"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkVBox" id="album_properties">
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkTable" id="table1">
+        <property name="visible">True</property>
+        <property name="n_rows">4</property>
+        <property name="n_columns">2</property>
+        <property name="column_spacing">6</property>
+        <property name="row_spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">_Name:</property>
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">name_entry</property>
+          </object>
+          <packing>
+            <property name="x_options">GTK_FILL</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="name_entry">
+            <property name="width_request">300</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="invisible_char">â??</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="right_attach">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label2">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">_Visibility:</property>
+            <property name="use_underline">True</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="GtkHBox" id="hbox1">
+            <property name="visible">True</property>
+            <child>
+              <object class="GtkComboBox" id="visibility_combobox">
+                <property name="visible">True</property>
+                <property name="model">visibility_liststore</property>
+                <property name="active">0</property>
+                <child>
+                  <object class="GtkCellRendererText" id="cellrenderertext1"/>
+                  <attributes>
+                    <attribute name="text">0</attribute>
+                  </attributes>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </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>
+        <child>
+          <object class="GtkEntry" id="description_entry">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="invisible_char">â??</property>
+          </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="GtkLabel" id="label3">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">D_escription:</property>
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">description_entry</property>
+          </object>
+          <packing>
+            <property name="top_attach">2</property>
+            <property name="bottom_attach">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="location_entry">
+            <property name="width_request">300</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="invisible_char">â??</property>
+            <property name="invisible_char_set">True</property>
+          </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="label4">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">_Location:</property>
+            <property name="use_underline">True</property>
+            <property name="mnemonic_widget">location_entry</property>
+          </object>
+          <packing>
+            <property name="top_attach">1</property>
+            <property name="bottom_attach">2</property>
+            <property name="x_options">GTK_FILL</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkListStore" id="visibility_liststore">
+    <columns>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+      <!-- column-name value -->
+      <column type="gint"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0" translatable="yes">Public photos</col>
+        <col id="1">0</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Private photos, visible to friends</col>
+        <col id="1">3</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Private</col>
+        <col id="1">4</col>
+      </row>
+    </data>
+  </object>
+</interface>
diff --git a/extensions/photobucket/dlg-export-to-photobucket.c b/extensions/photobucket/dlg-export-to-photobucket.c
index 5a6753f..6f28f1b 100644
--- a/extensions/photobucket/dlg-export-to-photobucket.c
+++ b/extensions/photobucket/dlg-export-to-photobucket.c
@@ -23,8 +23,12 @@
 #include <config.h>
 #include <gtk/gtk.h>
 #include <gthumb.h>
+#include <extensions/oauth/oauth-authentication.h>
 #include "dlg-export-to-photobucket.h"
+#include "photobucket-account.h"
+#include "photobucket-album-properties-dialog.h"
 #include "photobucket-consumer.h"
+#include "photobucket-service.h"
 
 
 #define GET_WIDGET(x) (_gtk_builder_get_widget (data->builder, (x)))
@@ -54,8 +58,8 @@ typedef struct {
 	GtkWidget           *progress_dialog;
 	OAuthConnection     *conn;
 	OAuthAuthentication *auth;
-	PhotobucktService   *service;
-	PhotobucketUser     *user;
+	PhotobucketService  *service;
+	PhotobucketAccount  *account;
 	GList               *albums;
 	PhotobucketAlbum    *album;
 	GList               *photos_ids;
@@ -73,7 +77,7 @@ export_dialog_destroy_cb (GtkWidget  *widget,
 	_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->account);
 	_g_object_unref (data->auth);
 	_g_object_unref (data->conn);
 	_g_object_unref (data->builder);
@@ -99,11 +103,12 @@ completed_messagedialog_response_cb (GtkDialog *dialog,
 
 	case _OPEN_IN_BROWSER_RESPONSE:
 		{
-			char   *url = NULL;
-			GError *error = NULL;
+			/*char   *url = NULL;
+			GError *error = NULL;*/
 
 			gtk_widget_destroy (GTK_WIDGET (dialog));
 
+			/* FIXME
 			if (data->album == NULL) {
 				GString *ids;
 				GList   *scan;
@@ -128,9 +133,11 @@ completed_messagedialog_response_cb (GtkDialog *dialog,
 					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);
+			/*g_free (url);*/
 		}
 		break;
 
@@ -163,103 +170,21 @@ export_completed_with_success (DialogData *data)
 
 
 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)
+upload_photos_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);
+	data->photos_ids = photobucket_service_upload_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);
+	export_completed_with_success (data);
 }
 
 
@@ -282,39 +207,29 @@ export_dialog_response_cb (GtkDialog *dialog,
 
 	case GTK_RESPONSE_OK:
 		{
-			char  *album_title;
-			GList *file_list;
+			GtkTreeIter  iter;
+			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);
-				}
+			if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (GET_WIDGET ("album_combobox")), &iter)) {
+				gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (GET_WIDGET ("album_combobox"))),
+						    &iter,
+						    ALBUM_DATA_COLUMN, &data->album,
+						    -1);
 			}
 
 			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);
+			photobucket_service_upload_photos (data->service,
+							   data->album,
+							   file_list,
+							   data->cancellable,
+							   upload_photos_ready_cb,
+							   data);
 
 			_g_object_list_unref (file_list);
-			g_free (album_title);
 		}
 		break;
 
@@ -327,19 +242,19 @@ export_dialog_response_cb (GtkDialog *dialog,
 static void
 update_account_list (DialogData *data)
 {
-	int            current_account_idx;
-	PhotobucketAccount *current_account;
-	int            idx;
-	GList         *scan;
-	GtkTreeIter    iter;
-	char          *free_space;
+	int           current_account_idx;
+	OAuthAccount *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;
+	current_account = oauth_authentication_get_account (data->auth);
+	for (scan = oauth_authentication_get_accounts (data->auth), idx = 0; scan; scan = scan->next, idx++) {
+		OAuthAccount *account = scan->data;
 
 		if ((current_account != NULL) && (g_strcmp0 (current_account->username, account->username) == 0))
 			current_account_idx = idx;
@@ -352,59 +267,64 @@ update_account_list (DialogData *data)
 	}
 	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);
+	free_space = g_format_size_for_display ((data->account->megabytes_allowed - data->account->megabytes_used) * (1024 * 1024));
 	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)
+authentication_accounts_changed_cb (OAuthAuthentication *auth,
+				    gpointer             user_data)
 {
 	update_account_list ((DialogData *) user_data);
 }
 
 
 static void
-album_list_ready_cb (GObject      *source_object,
-		     GAsyncResult *res,
-		     gpointer      user_data)
+update_album_list (DialogData *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;
-	}
+	GList *scan;
 
 	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;
+		char             *size;
 		GtkTreeIter       iter;
 
-		n_photos = g_strdup_printf ("(%d)", album->n_photos);
+		size = g_strdup_printf ("(%d)", album->photo_count); /* FIXME: show video_count as well */
 
 		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,
+				    ALBUM_TITLE_COLUMN, album->name,
+				    ALBUM_N_PHOTOS_COLUMN, size,
 				    -1);
 
-		g_free (n_photos);
+		g_free (size);
 	}
+}
+
+
+static void
+album_list_ready_cb (GObject      *source_object,
+		     GAsyncResult *res,
+		     gpointer      user_data)
+{
+	DialogData *data = user_data;
+	GError     *error = NULL;
 
-	gtk_widget_set_sensitive (GET_WIDGET ("upload_button"), TRUE);
+	_g_object_list_unref (data->albums);
+	data->albums = photobucket_service_get_albums_finish (data->service, 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;
+	}
+	update_album_list (data);
 
 	gth_task_dialog (GTH_TASK (data->conn), TRUE);
 
@@ -418,15 +338,15 @@ static void
 authentication_ready_cb (OAuthAuthentication *auth,
 			 DialogData          *data)
 {
-	_g_object_unref (data->user);
-	data->user = g_object_ref (user);
+	_g_object_unref (data->account);
+	data->account = g_object_ref (oauth_authentication_get_account (auth));
 	update_account_list (data);
 
-	photobucket_service_list_albums (data->service,
-				         NULL,
-				         data->cancellable,
-				         album_list_ready_cb,
-				         data);
+	photobucket_service_get_albums (data->service,
+					data->account,
+				        data->cancellable,
+				        album_list_ready_cb,
+				        data);
 }
 
 
@@ -461,6 +381,95 @@ account_combobox_changed_cb (GtkComboBox *widget,
 }
 
 
+static void
+album_combobox_changed_cb (GtkComboBox *widget,
+			   gpointer     user_data)
+{
+	DialogData  *data = user_data;
+	GtkTreeIter  iter;
+
+	gtk_widget_set_sensitive (GET_WIDGET ("upload_button"), gtk_combo_box_get_active_iter (widget, &iter));
+}
+
+
+static void
+create_album_ready_cb (GObject      *source_object,
+		       GAsyncResult *result,
+		       gpointer      user_data)
+{
+	DialogData       *data = user_data;
+	PhotobucketAlbum *album;
+	GError           *error = NULL;
+
+	album = photobucket_service_create_album_finish (data->service, result, &error);
+	if (error != NULL) {
+		if (data->conn != NULL)
+			gth_task_dialog (GTH_TASK (data->conn), TRUE);
+		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Could not create the album"), &error);
+		return;
+	}
+
+	data->albums = g_list_append (data->albums, album);
+	update_album_list (data);
+}
+
+
+static void
+new_album_dialog_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_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+
+	case GTK_RESPONSE_OK:
+		{
+			PhotobucketAlbum *album;
+
+			album = photobucket_album_new ();
+			photobucket_album_set_name (album, photobucket_album_properties_dialog_get_name (PHOTOBUCKET_ALBUM_PROPERTIES_DIALOG (dialog)));
+			photobucket_service_create_album (data->service,
+						          album,
+						          data->cancellable,
+						          create_album_ready_cb,
+						          data);
+
+			g_object_unref (album);
+		}
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+add_album_button_clicked_cb (GtkButton *button,
+			     gpointer   user_data)
+{
+	DialogData *data = user_data;
+	GtkWidget  *dialog;
+
+	dialog = photobucket_album_properties_dialog_new (g_file_info_get_edit_name (data->location->info));
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (new_album_dialog_response_cb),
+			  data);
+
+	gtk_window_set_title (GTK_WINDOW (dialog), _("New Album"));
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (data->dialog));
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_present (GTK_WINDOW (dialog));
+}
+
+
 void
 dlg_export_to_photobucket (GthBrowser *browser,
 			   GList      *file_list)
@@ -481,6 +490,31 @@ dlg_export_to_photobucket (GthBrowser *browser,
 	data->dialog = _gtk_builder_get_widget (data->builder, "export_dialog");
 	data->cancellable = g_cancellable_new ();
 
+	{
+		GtkCellLayout   *cell_layout;
+		GtkCellRenderer *renderer;
+
+		cell_layout = GTK_CELL_LAYOUT (GET_WIDGET ("album_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", ALBUM_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", ALBUM_TITLE_COLUMN,
+						NULL);
+
+		renderer = gtk_cell_renderer_text_new ();
+		gtk_cell_layout_pack_start (cell_layout, renderer, FALSE);
+		gtk_cell_layout_set_attributes (cell_layout, renderer,
+						"text", ALBUM_N_PHOTOS_COLUMN,
+						NULL);
+	}
+
 	data->file_list = NULL;
 	n_total = 0;
 	total_size = 0;
@@ -519,7 +553,7 @@ dlg_export_to_photobucket (GthBrowser *browser,
 
 	/* Set the widget data */
 
-	list_view = gth_file_list_new (GTH_FILE_LIST_TYPE_NO_SELECTION);
+	list_view = gth_file_list_new (GTH_FILE_LIST_TYPE_NO_SELECTION, FALSE);
 	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);
@@ -532,7 +566,7 @@ dlg_export_to_photobucket (GthBrowser *browser,
 	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"), photobucket_consumer->display_name);
+	title = g_strdup_printf (_("Export to %s"), photobucket_consumer.display_name);
 	gtk_window_set_title (GTK_WINDOW (data->dialog), title);
 	g_free (title);
 
@@ -554,10 +588,18 @@ dlg_export_to_photobucket (GthBrowser *browser,
 			  "changed",
 			  G_CALLBACK (account_combobox_changed_cb),
 			  data);
+	g_signal_connect (GET_WIDGET ("add_album_button"),
+			  "clicked",
+			  G_CALLBACK (add_album_button_clicked_cb),
+			  data);
+	g_signal_connect (GET_WIDGET ("album_combobox"),
+			  "changed",
+			  G_CALLBACK (album_combobox_changed_cb),
+			  data);
 
-	data->conn = oauth_connection_new (photobucket_consumer);
-	data->service = photobucket_service_new (data->conn);
+	data->conn = oauth_connection_new (&photobucket_consumer);
 	data->auth = oauth_authentication_new (data->conn,
+					       PHOTOBUCKET_TYPE_ACCOUNT,
 					       data->cancellable,
 					       GTK_WIDGET (data->browser),
 					       data->dialog);
@@ -570,6 +612,8 @@ dlg_export_to_photobucket (GthBrowser *browser,
 			  G_CALLBACK (authentication_accounts_changed_cb),
 			  data);
 
+	data->service = photobucket_service_new (data->conn);
+
 	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));
 
diff --git a/extensions/photobucket/photobucket-account.c b/extensions/photobucket/photobucket-account.c
new file mode 100644
index 0000000..bc3e0ef
--- /dev/null
+++ b/extensions/photobucket/photobucket-account.c
@@ -0,0 +1,209 @@
+/* -*- 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>
+#include <gthumb.h>
+#include "photobucket-account.h"
+
+
+static gpointer photobucket_account_parent_class = NULL;
+
+
+static void
+photobucket_account_finalize (GObject *obj)
+{
+	PhotobucketAccount *self;
+
+	self = PHOTOBUCKET_ACCOUNT (obj);
+
+	g_free (self->subdomain);
+	g_free (self->home_url);
+	g_free (self->album_url);
+
+	G_OBJECT_CLASS (photobucket_account_parent_class)->finalize (obj);
+}
+
+
+static void
+photobucket_account_class_init (PhotobucketAccountClass *klass)
+{
+	photobucket_account_parent_class = g_type_class_peek_parent (klass);
+	G_OBJECT_CLASS (klass)->finalize = photobucket_account_finalize;
+}
+
+
+static DomElement*
+photobucket_account_create_element (DomDomizable *base,
+				    DomDocument  *doc)
+{
+	PhotobucketAccount *self;
+	DomElement         *element;
+
+	self = PHOTOBUCKET_ACCOUNT (base);
+	element = oauth_account_create_element (DOM_DOMIZABLE (self), doc);
+	if (self->subdomain != NULL)
+		dom_element_set_attribute (element, "subdomain", self->subdomain);
+
+	return element;
+}
+
+
+static void
+photobucket_account_load_from_element (DomDomizable *base,
+				       DomElement   *element)
+{
+	PhotobucketAccount *self;
+
+	self = PHOTOBUCKET_ACCOUNT (base);
+
+	if (g_str_equal (element->tag_name, "content")) {
+		DomElement *node;
+
+		for (node = element->first_child; node; node = node->next_sibling) {
+			if (g_str_equal (node->tag_name, "album_url")) {
+				photobucket_account_set_album_url (self, dom_element_get_inner_text (node));
+			}
+			else if (g_str_equal (node->tag_name, "megabytes_used")) {
+				photobucket_account_set_megabytes_used (self, dom_element_get_inner_text (node));
+			}
+			else if (g_str_equal (node->tag_name, "megabytes_allowed")) {
+				photobucket_account_set_megabytes_allowed (self, dom_element_get_inner_text (node));
+			}
+			else if (g_str_equal (node->tag_name, "public")) {
+				photobucket_account_set_is_public (self, dom_element_get_inner_text (node));
+			}
+		}
+	}
+	else if (g_str_equal (element->tag_name, "account")) {
+		oauth_account_load_from_element (DOM_DOMIZABLE (self), element);
+		photobucket_account_set_subdomain (self, dom_element_get_attribute (element, "subdomain"));
+	}
+}
+
+
+static void
+photobucket_account_dom_domizable_interface_init (DomDomizableIface *iface)
+{
+	iface->create_element = photobucket_account_create_element;
+	iface->load_from_element = photobucket_account_load_from_element;
+}
+
+
+static void
+photobucket_account_instance_init (PhotobucketAccount *self)
+{
+	self->subdomain = NULL;
+	self->home_url = NULL;
+	self->album_url = NULL;
+}
+
+
+GType
+photobucket_account_get_type (void)
+{
+	static GType photobucket_account_type_id = 0;
+
+	if (photobucket_account_type_id == 0) {
+		static const GTypeInfo g_define_type_info = {
+			sizeof (PhotobucketAccountClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) photobucket_account_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (PhotobucketAccount),
+			0,
+			(GInstanceInitFunc) photobucket_account_instance_init,
+			NULL
+		};
+		static const GInterfaceInfo dom_domizable_info = {
+			(GInterfaceInitFunc) photobucket_account_dom_domizable_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		photobucket_account_type_id = g_type_register_static (OAUTH_TYPE_ACCOUNT,
+								      "PhotobucketAccount",
+								      &g_define_type_info,
+								      0);
+		g_type_add_interface_static (photobucket_account_type_id, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+	}
+
+	return photobucket_account_type_id;
+}
+
+
+OAuthAccount *
+photobucket_account_new (void)
+{
+	return g_object_new (PHOTOBUCKET_TYPE_ACCOUNT, NULL);
+}
+
+
+void
+photobucket_account_set_subdomain (PhotobucketAccount *self,
+				   const char         *value)
+{
+	_g_strset (&self->subdomain, value);
+}
+
+
+void
+photobucket_account_set_home_url (PhotobucketAccount *self,
+				  const char         *value)
+{
+	_g_strset (&self->home_url, value);
+}
+
+
+void
+photobucket_account_set_album_url (PhotobucketAccount *self,
+				   const char         *value)
+{
+	_g_strset (&self->album_url, value);
+}
+
+
+void
+photobucket_account_set_megabytes_used (PhotobucketAccount *self,
+				        const char         *value)
+{
+	self->megabytes_used = g_ascii_strtoull (value, NULL, 10);
+}
+
+
+void
+photobucket_account_set_megabytes_allowed (PhotobucketAccount *self,
+					   const char         *value)
+{
+	self->megabytes_allowed = g_ascii_strtoull (value, NULL, 10);
+}
+
+
+void
+photobucket_account_set_is_public (PhotobucketAccount *self,
+				   const char         *value)
+{
+	self->is_public =  g_str_equal (value, "1");
+}
diff --git a/extensions/photobucket/photobucket-account.h b/extensions/photobucket/photobucket-account.h
new file mode 100644
index 0000000..68230ec
--- /dev/null
+++ b/extensions/photobucket/photobucket-account.h
@@ -0,0 +1,74 @@
+/* -*- 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_ACCOUNT_H
+#define PHOTOBUCKET_ACCOUNT_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <extensions/oauth/oauth-account.h>
+
+G_BEGIN_DECLS
+
+#define PHOTOBUCKET_TYPE_ACCOUNT            (photobucket_account_get_type ())
+#define PHOTOBUCKET_ACCOUNT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), PHOTOBUCKET_TYPE_ACCOUNT, PhotobucketAccount))
+#define PHOTOBUCKET_ACCOUNT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), PHOTOBUCKET_TYPE_ACCOUNT, PhotobucketAccountClass))
+#define PHOTOBUCKET_IS_ACCOUNT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PHOTOBUCKET_TYPE_ACCOUNT))
+#define PHOTOBUCKET_IS_ACCOUNT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PHOTOBUCKET_TYPE_ACCOUNT))
+#define PHOTOBUCKET_ACCOUNT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), PHOTOBUCKET_TYPE_ACCOUNT, PhotobucketAccountClass))
+
+typedef struct _PhotobucketAccount PhotobucketAccount;
+typedef struct _PhotobucketAccountClass PhotobucketAccountClass;
+
+struct _PhotobucketAccount {
+	OAuthAccount parent_instance;
+
+	char     *subdomain;
+	char     *home_url;
+	char     *album_url;
+	goffset   megabytes_used;
+	goffset   megabytes_allowed;
+	gboolean  is_public;
+};
+
+struct _PhotobucketAccountClass {
+	OAuthAccountClass parent_class;
+};
+
+GType             photobucket_account_get_type               (void);
+OAuthAccount *    photobucket_account_new                    (void);
+void              photobucket_account_set_subdomain          (PhotobucketAccount *self,
+							      const char         *value);
+void              photobucket_account_set_home_url           (PhotobucketAccount *self,
+							      const char         *value);
+void              photobucket_account_set_album_url          (PhotobucketAccount *self,
+							      const char         *value);
+void              photobucket_account_set_megabytes_used     (PhotobucketAccount *self,
+							      const char         *value);
+void              photobucket_account_set_megabytes_allowed  (PhotobucketAccount *self,
+						              const char         *value);
+void              photobucket_account_set_is_public          (PhotobucketAccount *self,
+							      const char         *value);
+
+G_END_DECLS
+
+#endif /* PHOTOBUCKET_ACCOUNT_H */
diff --git a/extensions/photobucket/photobucket-album-properties-dialog.c b/extensions/photobucket/photobucket-album-properties-dialog.c
new file mode 100644
index 0000000..34012b1
--- /dev/null
+++ b/extensions/photobucket/photobucket-album-properties-dialog.c
@@ -0,0 +1,142 @@
+/* -*- 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 "photobucket-album-properties-dialog.h"
+
+
+#define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
+
+
+static gpointer parent_class = NULL;
+
+
+struct _PhotobucketAlbumPropertiesDialogPrivate {
+	GtkBuilder *builder;
+};
+
+
+static void
+photobucket_album_properties_dialog_finalize (GObject *object)
+{
+	PhotobucketAlbumPropertiesDialog *self;
+
+	self = PHOTOBUCKET_ALBUM_PROPERTIES_DIALOG (object);
+	_g_object_unref (self->priv->builder);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+photobucket_album_properties_dialog_class_init (PhotobucketAlbumPropertiesDialogClass *klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (PhotobucketAlbumPropertiesDialogPrivate));
+
+	object_class = (GObjectClass*) klass;
+	object_class->finalize = photobucket_album_properties_dialog_finalize;
+}
+
+
+static void
+photobucket_album_properties_dialog_init (PhotobucketAlbumPropertiesDialog *self)
+{
+	GtkWidget *content;
+
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, PHOTOBUCKET_TYPE_ALBUM_PROPERTIES_DIALOG, PhotobucketAlbumPropertiesDialogPrivate);
+	self->priv->builder = _gtk_builder_new_from_file ("photobucket-album-properties.ui", "photobucket");
+
+	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, "album_properties");
+	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_buttons (GTK_DIALOG (self),
+				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+				GTK_STOCK_OK, GTK_RESPONSE_OK,
+				NULL);
+	gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_OK);
+}
+
+
+GType
+photobucket_album_properties_dialog_get_type (void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo g_define_type_info = {
+			sizeof (PhotobucketAlbumPropertiesDialogClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) photobucket_album_properties_dialog_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (PhotobucketAlbumPropertiesDialog),
+			0,
+			(GInstanceInitFunc) photobucket_album_properties_dialog_init,
+			NULL
+		};
+		type = g_type_register_static (GTK_TYPE_DIALOG,
+					       "PhotobucketAlbumPropertiesDialog",
+					       &g_define_type_info,
+					       0);
+	}
+
+	return type;
+}
+
+
+static void
+photobucket_album_properties_dialog_construct (PhotobucketAlbumPropertiesDialog *self,
+				               const char                       *name)
+{
+	if (name != NULL)
+		gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("name_entry")), name);
+}
+
+
+GtkWidget *
+photobucket_album_properties_dialog_new (const char *name)
+{
+	PhotobucketAlbumPropertiesDialog *self;
+
+	self = g_object_new (PHOTOBUCKET_TYPE_ALBUM_PROPERTIES_DIALOG, NULL);
+	photobucket_album_properties_dialog_construct (self, name);
+
+	return (GtkWidget *) self;
+}
+
+
+const char *
+photobucket_album_properties_dialog_get_name (PhotobucketAlbumPropertiesDialog *self)
+{
+	return gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("name_entry")));
+}
diff --git a/extensions/photobucket/photobucket-album-properties-dialog.h b/extensions/photobucket/photobucket-album-properties-dialog.h
new file mode 100644
index 0000000..51944df
--- /dev/null
+++ b/extensions/photobucket/photobucket-album-properties-dialog.h
@@ -0,0 +1,58 @@
+/* -*- 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_ALBUM_PROPERTIES_DIALOG_H
+#define PHOTOBUCKET_ALBUM_PROPERTIES_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "photobucket-album.h"
+
+G_BEGIN_DECLS
+
+#define PHOTOBUCKET_TYPE_ALBUM_PROPERTIES_DIALOG            (photobucket_album_properties_dialog_get_type ())
+#define PHOTOBUCKET_ALBUM_PROPERTIES_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), PHOTOBUCKET_TYPE_ALBUM_PROPERTIES_DIALOG, PhotobucketAlbumPropertiesDialog))
+#define PHOTOBUCKET_ALBUM_PROPERTIES_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), PHOTOBUCKET_TYPE_ALBUM_PROPERTIES_DIALOG, PhotobucketAlbumPropertiesDialogClass))
+#define PHOTOBUCKET_IS_ALBUM_PROPERTIES_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PHOTOBUCKET_TYPE_ALBUM_PROPERTIES_DIALOG))
+#define PHOTOBUCKET_IS_ALBUM_PROPERTIES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PHOTOBUCKET_TYPE_ALBUM_PROPERTIES_DIALOG))
+#define PHOTOBUCKET_ALBUM_PROPERTIES_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), PHOTOBUCKET_TYPE_ALBUM_PROPERTIES_DIALOG, PhotobucketAlbumPropertiesDialogClass))
+
+typedef struct _PhotobucketAlbumPropertiesDialog PhotobucketAlbumPropertiesDialog;
+typedef struct _PhotobucketAlbumPropertiesDialogClass PhotobucketAlbumPropertiesDialogClass;
+typedef struct _PhotobucketAlbumPropertiesDialogPrivate PhotobucketAlbumPropertiesDialogPrivate;
+
+struct _PhotobucketAlbumPropertiesDialog {
+	GtkDialog parent_instance;
+	PhotobucketAlbumPropertiesDialogPrivate *priv;
+};
+
+struct _PhotobucketAlbumPropertiesDialogClass {
+	GtkDialogClass parent_class;
+};
+
+GType          photobucket_album_properties_dialog_get_type   (void);
+GtkWidget *    photobucket_album_properties_dialog_new        (const char *name);
+const char *   photobucket_album_properties_dialog_get_name   (PhotobucketAlbumPropertiesDialog *self);
+
+G_END_DECLS
+
+#endif /* PHOTOBUCKET_ALBUM_PROPERTIES_DIALOG_H */
diff --git a/extensions/photobucket/photobucket-album.c b/extensions/photobucket/photobucket-album.c
new file mode 100644
index 0000000..778854f
--- /dev/null
+++ b/extensions/photobucket/photobucket-album.c
@@ -0,0 +1,149 @@
+/* -*- 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>
+#include <gthumb.h>
+#include "photobucket-album.h"
+
+
+static gpointer photobucket_album_parent_class = NULL;
+
+
+static void
+photobucket_album_finalize (GObject *obj)
+{
+	PhotobucketAlbum *self;
+
+	self = PHOTOBUCKET_ALBUM (obj);
+
+	g_free (self->name);
+
+	G_OBJECT_CLASS (photobucket_album_parent_class)->finalize (obj);
+}
+
+
+static void
+photobucket_album_class_init (PhotobucketAlbumClass *klass)
+{
+	photobucket_album_parent_class = g_type_class_peek_parent (klass);
+	G_OBJECT_CLASS (klass)->finalize = photobucket_album_finalize;
+}
+
+
+static DomElement*
+photobucket_album_create_element (DomDomizable *base,
+			          DomDocument  *doc)
+{
+	PhotobucketAlbum *self;
+	DomElement    *element;
+
+	self = PHOTOBUCKET_ALBUM (base);
+
+	element = dom_document_create_element (doc, "photoset", NULL);
+	if (self->name != NULL)
+		dom_element_append_child (element, dom_document_create_element_with_text (doc, self->name, "name", NULL));
+
+	return element;
+}
+
+
+static void
+photobucket_album_load_from_element (DomDomizable *base,
+				     DomElement   *element)
+{
+	PhotobucketAlbum *self;
+
+	self = PHOTOBUCKET_ALBUM (base);
+
+	photobucket_album_set_name (self, dom_element_get_attribute (element, "name"));
+	self->photo_count = atoi (dom_element_get_attribute (element, "photo_count"));
+	self->video_count = atoi (dom_element_get_attribute (element, "video_count"));
+}
+
+
+static void
+photobucket_album_dom_domizable_interface_init (DomDomizableIface *iface)
+{
+	iface->create_element = photobucket_album_create_element;
+	iface->load_from_element = photobucket_album_load_from_element;
+}
+
+
+static void
+photobucket_album_instance_init (PhotobucketAlbum *self)
+{
+	self->name = NULL;
+	self->photo_count = 0;
+	self->video_count = 0;
+}
+
+
+GType
+photobucket_album_get_type (void)
+{
+	static GType photobucket_album_type_id = 0;
+
+	if (photobucket_album_type_id == 0) {
+		static const GTypeInfo g_define_type_info = {
+			sizeof (PhotobucketAlbumClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) photobucket_album_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (PhotobucketAlbum),
+			0,
+			(GInstanceInitFunc) photobucket_album_instance_init,
+			NULL
+		};
+		static const GInterfaceInfo dom_domizable_info = {
+			(GInterfaceInitFunc) photobucket_album_dom_domizable_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		photobucket_album_type_id = g_type_register_static (G_TYPE_OBJECT,
+								 "PhotobucketAlbum",
+								 &g_define_type_info,
+								 0);
+		g_type_add_interface_static (photobucket_album_type_id, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+	}
+
+	return photobucket_album_type_id;
+}
+
+
+PhotobucketAlbum *
+photobucket_album_new (void)
+{
+	return g_object_new (PHOTOBUCKET_TYPE_ALBUM, NULL);
+}
+
+
+void
+photobucket_album_set_name (PhotobucketAlbum *self,
+			    const char       *value)
+{
+	_g_strset (&self->name, value);
+}
diff --git a/extensions/photobucket/photobucket-album.h b/extensions/photobucket/photobucket-album.h
new file mode 100644
index 0000000..19803cd
--- /dev/null
+++ b/extensions/photobucket/photobucket-album.h
@@ -0,0 +1,60 @@
+/* -*- 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_ALBUM_H
+#define PHOTOBUCKET_ALBUM_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PHOTOBUCKET_TYPE_ALBUM            (photobucket_album_get_type ())
+#define PHOTOBUCKET_ALBUM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), PHOTOBUCKET_TYPE_ALBUM, PhotobucketAlbum))
+#define PHOTOBUCKET_ALBUM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), PHOTOBUCKET_TYPE_ALBUM, PhotobucketAlbumClass))
+#define PHOTOBUCKET_IS_ALBUM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PHOTOBUCKET_TYPE_ALBUM))
+#define PHOTOBUCKET_IS_ALBUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PHOTOBUCKET_TYPE_ALBUM))
+#define PHOTOBUCKET_ALBUM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), PHOTOBUCKET_TYPE_ALBUM, PhotobucketAlbumClass))
+
+typedef struct _PhotobucketAlbum PhotobucketAlbum;
+typedef struct _PhotobucketAlbumClass PhotobucketAlbumClass;
+
+struct _PhotobucketAlbum {
+	GObject parent_instance;
+
+	char *name;
+	int   photo_count;
+	int   video_count;
+};
+
+struct _PhotobucketAlbumClass {
+	GObjectClass parent_class;
+};
+
+GType              photobucket_album_get_type  (void);
+PhotobucketAlbum * photobucket_album_new       (void);
+void               photobucket_album_set_name  (PhotobucketAlbum *self,
+						const char       *value);
+
+G_END_DECLS
+
+#endif /* PHOTOBUCKET_ALBUM_H */
diff --git a/extensions/photobucket/photobucket-consumer.c b/extensions/photobucket/photobucket-consumer.c
index efa9fe3..2490e20 100644
--- a/extensions/photobucket/photobucket-consumer.c
+++ b/extensions/photobucket/photobucket-consumer.c
@@ -24,13 +24,15 @@
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <gthumb.h>
+#include "photobucket-account.h"
 #include "photobucket-consumer.h"
 
 
-static gboolean
-photobucket_utils_parse_response (SoupBuffer   *body,
-				  DomDocument **doc_p,
-				  GError      **error)
+gboolean
+photobucket_utils_parse_response (SoupBuffer          *body,
+				  DomDocument        **doc_p,
+				  GSimpleAsyncResult  *result,
+				  GError             **error)
 {
 	DomDocument *doc;
 	DomElement  *node;
@@ -61,8 +63,8 @@ photobucket_utils_parse_response (SoupBuffer   *body,
 			}
 
 			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);
+				*error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 999, _("Unknown error"));
+				g_simple_async_result_set_from_error (result, *error);
 			}
 			else if (strcmp (status, "Exception") == 0) {
 				*error = g_error_new_literal (OAUTH_CONNECTION_ERROR,
@@ -70,8 +72,10 @@ photobucket_utils_parse_response (SoupBuffer   *body,
 							      (message != NULL) ? message : _("Unknown error"));
 			}
 
-			g_object_unref (doc);
-			return FALSE;
+			if (*error != NULL) {
+				g_object_unref (doc);
+				return FALSE;
+			}
 		}
 	}
 
@@ -87,40 +91,25 @@ photobucket_login_request_response (OAuthConnection    *self,
 				    SoupBuffer         *body,
 				    GSimpleAsyncResult *result)
 {
-	DomDocument *doc = NULL;
-	GError      *error = NULL;
-
-	if (photobucket_utils_parse_response (body, &doc, &error)) {
-		/*OAuthAccount *account;
-		char         *username;*/
-
-		/* FIXME: set the connection token and token_secret from the response */
-
-		/*account = oauth_account_new ();
-		oauth_account_set_username (account, username);
-		oauth_account_set_token (account, oauth_connection_get_token (self));
-		oauth_account_set_token_secret (account, oauth_connection_get_token_secret (self));*/
-
-		char *token;
-		char *token_secret;
-
-		token = NULL;
-		token_secret = NULL;
-
-		/* FIXME: parse the body and set the token */
-		/*oauth_connection_set_token (self, token, token_secret);*/
-
-		if ((token == NULL) || (token_secret == NULL)) {
-			error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 0, _("Unknown error"));
-			g_simple_async_result_set_from_error (result, error);
-		}
-		else
-			g_simple_async_result_set_op_res_gboolean (self->priv->result, TRUE);
-
-		g_object_unref (doc);
+	GHashTable *values;
+	char       *token;
+	char       *token_secret;
+
+	values = soup_form_decode (body->data);
+	token = g_hash_table_lookup (values, "oauth_token");
+	token_secret = g_hash_table_lookup (values, "oauth_token_secret");
+	if ((token != NULL) && (token_secret != NULL)) {
+		oauth_connection_set_token (self, token, token_secret);
+		g_simple_async_result_set_op_res_gboolean (result, TRUE);
 	}
-	else
+	else {
+		GError *error;
+
+		error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 0, _("Unknown error"));
 		g_simple_async_result_set_from_error (result, error);
+	}
+
+	g_hash_table_destroy (values);
 }
 
 
@@ -145,33 +134,49 @@ photobucket_get_access_token_response (OAuthConnection    *self,
 				       SoupBuffer         *body,
 				       GSimpleAsyncResult *result)
 {
-	DomDocument *doc = NULL;
-	GError      *error = NULL;
+	GHashTable *values;
+	char       *username;
+	char       *token;
+	char       *token_secret;
+
+	values = soup_form_decode (body->data);
 
-	if (photobucket_utils_parse_response (body, &doc, &error)) {
-		OAuthAccount *account = NULL;
-		char         *username;
+	username = g_hash_table_lookup (values, "username");
+	token = g_hash_table_lookup (values, "oauth_token");
+	token_secret = g_hash_table_lookup (values, "oauth_token_secret");
+	if ((username != NULL) && (token != NULL) && (token_secret != NULL)) {
+		OAuthAccount *account;
 
-		/* FIXME: parse the body and set the account */
+		oauth_connection_set_token (self, token, token_secret);
 
-		/*account = oauth_account_new ();
+		account = photobucket_account_new ();
 		oauth_account_set_username (account, username);
-		oauth_account_set_token (account, oauth_connection_get_token (self));
-		oauth_account_set_token_secret (account, oauth_connection_get_token_secret (self));*/
+		oauth_account_set_token (account, token);
+		oauth_account_set_token_secret (account, token_secret);
+		photobucket_account_set_subdomain (PHOTOBUCKET_ACCOUNT (account), g_hash_table_lookup (values, "subdomain"));
+		photobucket_account_set_home_url (PHOTOBUCKET_ACCOUNT (account), g_hash_table_lookup (values, "homeurl"));
+		g_simple_async_result_set_op_res_gpointer (result, account, g_object_unref);
+	}
+	else {
+		GError *error;
+
+		error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 0, _("Unknown error"));
+		g_simple_async_result_set_from_error (result, error);
+	}
 
-		/*oauth_connection_set_token (self, token, token_secret);*/
+	g_hash_table_destroy (values);
+}
 
-		if (account == NULL) {
-			error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 0, _("Unknown error"));
-			g_simple_async_result_set_from_error (result, error);
-		}
-		else
-			g_simple_async_result_set_op_res_gpointer (self->priv->result, account, g_object_unref);
 
-		g_object_unref (doc);
-	}
+static char *
+photobucket_get_check_token_url (OAuthConnection *self,
+				 OAuthAccount    *account,
+			         gboolean         for_signature)
+{
+	if (for_signature)
+		return g_strconcat ("http://api.photobucket.com/user/";, account->username, NULL);
 	else
-		g_simple_async_result_set_from_error (result, error);
+		return g_strconcat ("http://";, PHOTOBUCKET_ACCOUNT (account)->subdomain, "/user/", account->username, NULL);
 }
 
 
@@ -179,14 +184,30 @@ static void
 photobucket_check_token_response (OAuthConnection    *self,
 				  SoupMessage        *msg,
 				  SoupBuffer         *body,
-				  GSimpleAsyncResult *result)
+				  GSimpleAsyncResult *result,
+				  OAuthAccount       *account)
 {
 	DomDocument *doc = NULL;
 	GError      *error = NULL;
 
-	if (photobucket_utils_parse_response (body, &doc, &error)) {
-		/* FIXME: add other user info to the account */
-		g_simple_async_result_set_op_res_gboolean (self->priv->result, TRUE);
+	if (photobucket_utils_parse_response (body, &doc, result, &error)) {
+		DomElement *node;
+
+		for (node = DOM_ELEMENT (doc)->first_child; node; node = node->next_sibling) {
+			if (g_strcmp0 (node->tag_name, "response") == 0) {
+				DomElement *child;
+
+				for (child = DOM_ELEMENT (node)->first_child; child; child = child->next_sibling) {
+					if (g_strcmp0 (child->tag_name, "content") == 0) {
+						dom_domizable_load_from_element (DOM_DOMIZABLE (account), child);
+						break;
+					}
+				}
+				break;
+			}
+		}
+
+		g_simple_async_result_set_op_res_gboolean (result, TRUE);
 		g_object_unref (doc);
 	}
 	else
@@ -195,17 +216,17 @@ photobucket_check_token_response (OAuthConnection    *self,
 
 
 OAuthConsumer photobucket_consumer = {
-	"PhotoBucket",
+	"Photobucket",
 	"photobucket",
 	"http://www.photobucket.com";,
 	"http",
-	"8960706ee7f4151e893b11837e9c24ce",
-	"1ff8d1e45c873423",
+	"149829931",
+	"b4e542229836cc59b66489c6d2d8ca04",
 	"http://api.photobucket.com/login/request";,
 	photobucket_login_request_response,
 	phoyobucket_get_login_link,
 	"http://api.photobucket.com/login/access";,
 	photobucket_get_access_token_response,
-	"http://api.photobucket.com/user/";,
+	photobucket_get_check_token_url,
 	photobucket_check_token_response
 };
diff --git a/extensions/photobucket/photobucket-consumer.h b/extensions/photobucket/photobucket-consumer.h
index 25a2515..8cbebb6 100644
--- a/extensions/photobucket/photobucket-consumer.h
+++ b/extensions/photobucket/photobucket-consumer.h
@@ -23,8 +23,14 @@
 #ifndef PHOTOBUCKET_CONSUMER_H
 #define PHOTOBUCKET_CONSUMER_H
 
+#include <gthumb.h>
 #include <extensions/oauth/oauth-connection.h>
 
 extern OAuthConsumer photobucket_consumer;
 
+gboolean  photobucket_utils_parse_response (SoupBuffer          *body,
+				            DomDocument        **doc_p,
+				            GSimpleAsyncResult  *result,
+				            GError             **error);
+
 #endif /* PHOTOBUCKET_CONSUMER_H */
diff --git a/extensions/photobucket/photobucket-photo.c b/extensions/photobucket/photobucket-photo.c
new file mode 100644
index 0000000..054d298
--- /dev/null
+++ b/extensions/photobucket/photobucket-photo.c
@@ -0,0 +1,231 @@
+/* -*- 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>
+#include <gthumb.h>
+#include "photobucket-photo.h"
+
+
+static gpointer photobucket_photo_parent_class = NULL;
+
+
+static void
+photobucket_photo_finalize (GObject *obj)
+{
+	PhotobucketPhoto *self;
+
+	self = PHOTOBUCKET_PHOTO (obj);
+
+	g_free (self->name);
+	g_free (self->browse_url);
+	g_free (self->url);
+	g_free (self->thumb_url);
+	g_free (self->description);
+	g_free (self->title);
+
+	G_OBJECT_CLASS (photobucket_photo_parent_class)->finalize (obj);
+}
+
+
+static void
+photobucket_photo_class_init (PhotobucketPhotoClass *klass)
+{
+	photobucket_photo_parent_class = g_type_class_peek_parent (klass);
+	G_OBJECT_CLASS (klass)->finalize = photobucket_photo_finalize;
+}
+
+
+static DomElement*
+photobucket_photo_create_element (DomDomizable *base,
+				DomDocument  *doc)
+{
+	PhotobucketPhoto *self;
+	DomElement  *element;
+
+	self = PHOTOBUCKET_PHOTO (base);
+
+	element = dom_document_create_element (doc, "photo", NULL);
+	if (self->name != NULL)
+		dom_element_set_attribute (element, "name", self->name);
+
+	return element;
+}
+
+
+static void
+photobucket_photo_load_from_element (DomDomizable *base,
+				     DomElement   *element)
+{
+	PhotobucketPhoto *self;
+	DomElement       *node;
+
+	if ((element == NULL) || (g_strcmp0 (element->tag_name, "photo") != 0))
+		return;
+
+	self = PHOTOBUCKET_PHOTO (base);
+
+	photobucket_photo_set_name (self, dom_element_get_attribute (element, "name"));
+	photobucket_photo_set_is_public (self, dom_element_get_attribute (element, "public"));
+
+	for (node = element->first_child; node; node = node->next_sibling) {
+		if (g_strcmp0 (node->tag_name, "browse_url") == 0) {
+			photobucket_photo_set_browse_url (self, dom_element_get_inner_text (node));
+		}
+		else if (g_strcmp0 (node->tag_name, "url") == 0) {
+			photobucket_photo_set_url (self, dom_element_get_inner_text (node));
+		}
+		else if (g_strcmp0 (node->tag_name, "thumb_url") == 0) {
+			photobucket_photo_set_thumb_url (self, dom_element_get_inner_text (node));
+		}
+		else if (g_strcmp0 (node->tag_name, "description") == 0) {
+			photobucket_photo_set_description (self, dom_element_get_inner_text (node));
+		}
+		else if (g_strcmp0 (node->tag_name, "title") == 0) {
+			photobucket_photo_set_title (self, dom_element_get_inner_text (node));
+		}
+		else if (g_strcmp0 (node->tag_name, "is_sponsored") == 0) {
+			photobucket_photo_set_is_sponsored (self, dom_element_get_inner_text (node));
+		}
+	}
+}
+
+
+static void
+photobucket_photo_dom_domizable_interface_init (DomDomizableIface *iface)
+{
+	iface->create_element = photobucket_photo_create_element;
+	iface->load_from_element = photobucket_photo_load_from_element;
+}
+
+
+static void
+photobucket_photo_instance_init (PhotobucketPhoto *self)
+{
+}
+
+
+GType
+photobucket_photo_get_type (void)
+{
+	static GType photobucket_photo_type_id = 0;
+
+	if (photobucket_photo_type_id == 0) {
+		static const GTypeInfo g_define_type_info = {
+			sizeof (PhotobucketPhotoClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) photobucket_photo_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (PhotobucketPhoto),
+			0,
+			(GInstanceInitFunc) photobucket_photo_instance_init,
+			NULL
+		};
+		static const GInterfaceInfo dom_domizable_info = {
+			(GInterfaceInitFunc) photobucket_photo_dom_domizable_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		photobucket_photo_type_id = g_type_register_static (G_TYPE_OBJECT,
+								    "PhotobucketPhoto",
+								    &g_define_type_info,
+								    0);
+		g_type_add_interface_static (photobucket_photo_type_id, DOM_TYPE_DOMIZABLE, &dom_domizable_info);
+	}
+
+	return photobucket_photo_type_id;
+}
+
+
+PhotobucketPhoto *
+photobucket_photo_new (void)
+{
+	return g_object_new (PHOTOBUCKET_TYPE_PHOTO, NULL);
+}
+
+
+void
+photobucket_photo_set_name (PhotobucketPhoto *self,
+			    const char       *value)
+{
+	_g_strset (&self->name, value);
+}
+
+
+void
+photobucket_photo_set_is_public (PhotobucketPhoto *self,
+			         const char       *value)
+{
+	self->is_public = (g_strcmp0 (value, "1") == 0);
+}
+
+
+void
+photobucket_photo_set_browse_url (PhotobucketPhoto *self,
+			          const char       *value)
+{
+	_g_strset (&self->name, value);
+}
+
+
+void
+photobucket_photo_set_url (PhotobucketPhoto *self,
+			   const char       *value)
+{
+	_g_strset (&self->name, value);
+}
+
+
+void
+photobucket_photo_set_thumb_url (PhotobucketPhoto *self,
+			         const char       *value)
+{
+	_g_strset (&self->name, value);
+}
+
+
+void
+photobucket_photo_set_description (PhotobucketPhoto *self,
+			           const char       *value)
+{
+	_g_strset (&self->name, value);
+}
+
+
+void
+photobucket_photo_set_title (PhotobucketPhoto *self,
+			     const char       *value)
+{
+	_g_strset (&self->name, value);
+}
+
+
+void
+photobucket_photo_set_is_sponsored (PhotobucketPhoto *self,
+			            const char       *value)
+{
+	self->is_sponsored = (g_strcmp0 (value, "1") == 0);
+}
diff --git a/extensions/photobucket/photobucket-photo.h b/extensions/photobucket/photobucket-photo.h
new file mode 100644
index 0000000..2919131
--- /dev/null
+++ b/extensions/photobucket/photobucket-photo.h
@@ -0,0 +1,81 @@
+/* -*- 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_PHOTO_H
+#define PHOTOBUCKET_PHOTO_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PHOTOBUCKET_TYPE_PHOTO            (photobucket_photo_get_type ())
+#define PHOTOBUCKET_PHOTO(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), PHOTOBUCKET_TYPE_PHOTO, PhotobucketPhoto))
+#define PHOTOBUCKET_PHOTO_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), PHOTOBUCKET_TYPE_PHOTO, PhotobucketPhotoClass))
+#define PHOTOBUCKET_IS_PHOTO(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PHOTOBUCKET_TYPE_PHOTO))
+#define PHOTOBUCKET_IS_PHOTO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PHOTOBUCKET_TYPE_PHOTO))
+#define PHOTOBUCKET_PHOTO_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), PHOTOBUCKET_TYPE_PHOTO, PhotobucketPhotoClass))
+
+typedef struct _PhotobucketPhoto PhotobucketPhoto;
+typedef struct _PhotobucketPhotoClass PhotobucketPhotoClass;
+typedef struct _PhotobucketPhotoPrivate PhotobucketPhotoPrivate;
+
+struct _PhotobucketPhoto {
+	GObject parent_instance;
+	PhotobucketPhotoPrivate *priv;
+
+	char     *name;
+	gboolean  is_public;
+	char     *browse_url;
+	char     *url;
+	char     *thumb_url;
+	char     *description;
+	char     *title;
+	gboolean  is_sponsored;
+};
+
+struct _PhotobucketPhotoClass {
+	GObjectClass parent_class;
+};
+
+GType                photobucket_photo_get_type          (void);
+PhotobucketPhoto *   photobucket_photo_new               (void);
+void                 photobucket_photo_set_name          (PhotobucketPhoto *self,
+					                  const char       *value);
+void                 photobucket_photo_set_is_public     (PhotobucketPhoto *self,
+			                                  const char       *value);
+void                 photobucket_photo_set_browse_url    (PhotobucketPhoto *self,
+					                  const char       *value);
+void                 photobucket_photo_set_url           (PhotobucketPhoto *self,
+					                  const char       *value);
+void                 photobucket_photo_set_thumb_url     (PhotobucketPhoto *self,
+					                  const char       *value);
+void                 photobucket_photo_set_description   (PhotobucketPhoto *self,
+					                  const char       *value);
+void                 photobucket_photo_set_title         (PhotobucketPhoto *self,
+					                  const char       *value);
+void                 photobucket_photo_set_is_sponsored  (PhotobucketPhoto *self,
+			                                  const char       *value);
+
+G_END_DECLS
+
+#endif /* PHOTOBUCKET_PHOTO_H */
diff --git a/extensions/photobucket/photobucket-service.c b/extensions/photobucket/photobucket-service.c
new file mode 100644
index 0000000..d67ea50
--- /dev/null
+++ b/extensions/photobucket/photobucket-service.c
@@ -0,0 +1,989 @@
+/* -*- 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-album.h"
+#include "photobucket-consumer.h"
+#include "photobucket-photo.h"
+#include "photobucket-service.h"
+
+
+typedef struct {
+	PhotobucketAlbum    *album;
+	GList               *file_list;
+	GCancellable        *cancellable;
+        GAsyncReadyCallback  callback;
+        gpointer             user_data;
+	GList               *current;
+	goffset              total_size;
+	goffset              uploaded_size;
+	int                  n_files;
+	int                  uploaded_files;
+	GList               *ids;
+} PostPhotosData;
+
+
+static void
+post_photos_data_free (PostPhotosData *post_photos)
+{
+	if (post_photos == NULL)
+		return;
+	_g_string_list_free (post_photos->ids);
+	_g_object_unref (post_photos->cancellable);
+	_g_object_list_unref (post_photos->file_list);
+	g_free (post_photos);
+}
+
+
+struct _PhotobucketServicePrivate
+{
+	OAuthConnection *conn;
+	PostPhotosData  *post_photos;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+photobucket_service_finalize (GObject *object)
+{
+	PhotobucketService *self;
+
+	self = PHOTOBUCKET_SERVICE (object);
+
+	_g_object_unref (self->priv->conn);
+	post_photos_data_free (self->priv->post_photos);
+
+	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->post_photos = 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 (OAuthConnection *conn)
+{
+	PhotobucketService *self;
+
+	self = (PhotobucketService *) g_object_new (PHOTOBUCKET_TYPE_SERVICE, NULL);
+	self->priv->conn = g_object_ref (conn);
+
+	return self;
+}
+
+
+#if 0
+
+
+/* -- photobucket_service_get_user_info -- */
+
+
+static void
+get_logged_in_user_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, result, &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 (OAUTH_CONNECTION_ERROR, 0, _("Unknown error"));
+			g_simple_async_result_set_from_error (result, error);
+		}
+		else
+			g_simple_async_result_set_op_res_gpointer (result, uid, g_free);
+
+		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_get_logged_in_user (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);
+	g_hash_table_insert (data_set, "method", "photobucket.users.getLoggedInUser");
+	oauth_connection_add_signature (self->priv->conn, "photobucket.users.getLoggedInUser", );
+	msg = soup_form_request_new_from_hash ("POST", PHOTOBUCKET_HTTPS_REST_SERVER, data_set);
+	oauth_connection_send_message (self->priv->conn,
+				       msg,
+				       cancellable,
+				       callback,
+				       user_data,
+				       photobucket_service_get_logged_in_user,
+				       get_logged_in_user_ready_cb,
+				       self);
+
+	g_hash_table_destroy (data_set);
+}
+
+
+char *
+photobucket_service_get_logged_in_user_finish (PhotobucketService  *self,
+					       GAsyncResult        *result,
+					       GError             **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+	else
+		return g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)));
+}
+
+
+/* -- photobucket_service_get_user_info -- */
+
+
+static void
+get_user_info_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   *node;
+		PhotobucketUser *user = NULL;
+
+		for (node = DOM_ELEMENT (doc)->first_child; node; node = node->next_sibling) {
+			if (g_strcmp0 (node->tag_name, "users_getInfo_response") == 0) {
+				DomElement *child;
+
+				for (child = node->first_child; child; child = child->next_sibling) {
+					if (g_strcmp0 (child->tag_name, "user") == 0) {
+						user = photobucket_user_new ();
+						dom_domizable_load_from_element (DOM_DOMIZABLE (user), child);
+						g_simple_async_result_set_op_res_gpointer (result, user, (GDestroyNotify) g_object_unref);
+					}
+				}
+			}
+		}
+
+		if (user == NULL) {
+			error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 0, _("Unknown error"));
+			g_simple_async_result_set_from_error (result, error);
+		}
+
+		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_get_user_info (PhotobucketService  *self,
+				   const char          *fields,
+				   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);
+	g_hash_table_insert (data_set, "method", "photobucket.users.getInfo");
+	g_hash_table_insert (data_set, "uids", (char *) oauth_connection_get_user_id (self->priv->conn));
+	g_hash_table_insert (data_set, "fields", (char *) fields);
+	oauth_connection_add_api_sig (self->priv->conn, data_set);
+	msg = soup_form_request_new_from_hash ("POST", PHOTOBUCKET_HTTPS_REST_SERVER, data_set);
+	oauth_connection_send_message (self->priv->conn,
+				       msg,
+				       cancellable,
+				       callback,
+				       user_data,
+				       photobucket_service_get_user_info,
+				       get_user_info_ready_cb,
+				       self);
+
+	g_hash_table_destroy (data_set);
+}
+
+
+PhotobucketUser *
+photobucket_service_get_user_info_finish (PhotobucketService  *self,
+				          GAsyncResult        *result,
+				          GError             **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+	else
+		return g_object_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)));
+}
+
+
+#endif
+
+
+/* -- photobucket_service_get_albums -- */
+
+
+static DomElement *
+get_content (DomDocument *doc)
+{
+	DomElement *root;
+
+	for (root = DOM_ELEMENT (doc)->first_child; root; root = root->next_sibling) {
+		if (g_strcmp0 (root->tag_name, "response") == 0) {
+			DomElement *child;
+
+			for (child = root->first_child; child; child = child->next_sibling)
+				if (g_strcmp0 (child->tag_name, "content") == 0)
+					return child;
+		}
+	}
+
+	g_assert_not_reached ();
+
+	return NULL;
+}
+
+
+static void
+get_albums_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, result, &error)) {
+		GList      *albums = NULL;
+		DomElement *node;
+
+		for (node = get_content (doc)->first_child; node; node = node->next_sibling) {
+			if (g_strcmp0 (node->tag_name, "album") == 0) {
+				PhotobucketAlbum *album;
+
+				album = photobucket_album_new ();
+				dom_domizable_load_from_element (DOM_DOMIZABLE (album), node);
+				albums = g_list_prepend (albums, album);
+			}
+		}
+
+		albums = g_list_reverse (albums);
+		g_simple_async_result_set_op_res_gpointer (result, albums, (GDestroyNotify) _g_object_list_unref);
+
+		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_get_albums (PhotobucketService  *self,
+			        PhotobucketAccount  *account,
+			        GCancellable        *cancellable,
+			        GAsyncReadyCallback  callback,
+			        gpointer             user_data)
+{
+	GHashTable  *data_set;
+	char        *url;
+	SoupMessage *msg;
+
+	g_return_if_fail (account != NULL);
+	g_return_if_fail (account->subdomain != NULL);
+
+	gth_task_progress (GTH_TASK (self->priv->conn), _("Getting the album list"), NULL, TRUE, 0.0);
+
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+	url = g_strconcat ("http://api.photobucket.com/album/";, OAUTH_ACCOUNT (account)->username, NULL);
+	oauth_connection_add_signature (self->priv->conn, "GET", url, data_set);
+	g_free (url);
+
+	url = g_strconcat ("http://";, account->subdomain, "/album/", OAUTH_ACCOUNT (account)->username, NULL);
+	msg = soup_form_request_new_from_hash ("GET", url, data_set);
+	oauth_connection_send_message (self->priv->conn,
+				       msg,
+				       cancellable,
+				       callback,
+				       user_data,
+				       photobucket_service_get_albums,
+				       get_albums_ready_cb,
+				       self);
+
+	g_hash_table_destroy (data_set);
+	g_free (url);
+}
+
+
+GList *
+photobucket_service_get_albums_finish (PhotobucketService  *service,
+				       GAsyncResult        *result,
+				       GError             **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+	else
+		return _g_object_list_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)));
+}
+
+
+/* -- photobucket_service_create_album -- */
+
+
+static void
+create_album_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, result, &error)) {
+		DomElement    *node;
+		PhotobucketAlbum *album = NULL;
+
+		for (node = DOM_ELEMENT (doc)->first_child; node; node = node->next_sibling) {
+			if (g_strcmp0 (node->tag_name, "photos_createAlbum_response") == 0) {
+				album = photobucket_album_new ();
+				dom_domizable_load_from_element (DOM_DOMIZABLE (album), node);
+				break;
+			}
+		}
+
+		if (album == NULL) {
+			error = g_error_new_literal (OAUTH_CONNECTION_ERROR, 0, _("Unknown error"));
+			g_simple_async_result_set_from_error (result, error);
+		}
+		else
+			g_simple_async_result_set_op_res_gpointer (result, album, (GDestroyNotify) _g_object_unref);
+
+		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_create_album (PhotobucketService  *self,
+			          PhotobucketAlbum    *album,
+			          GCancellable        *cancellable,
+			          GAsyncReadyCallback  callback,
+			          gpointer             user_data)
+{
+	GHashTable  *data_set;
+	const char  *privacy;
+	SoupMessage *msg;
+
+	g_return_if_fail (album != NULL);
+	g_return_if_fail (album->name != NULL);
+
+	gth_task_progress (GTH_TASK (self->priv->conn), _("Creating the new album"), NULL, TRUE, 0.0);
+
+	/* FIXME
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (data_set, "method", "photobucket.photos.createAlbum");
+	g_hash_table_insert (data_set, "name", album->name);
+	if (album->description != NULL)
+		g_hash_table_insert (data_set, "description", album->description);
+	if (album->location != NULL)
+		g_hash_table_insert (data_set, "location", album->location);
+	privacy = get_privacy_from_visibility (album->visibility);
+	if (privacy != NULL)
+		g_hash_table_insert (data_set, "privacy", (char *) privacy);
+	oauth_connection_add_api_sig (self->priv->conn, data_set);
+	msg = soup_form_request_new_from_hash ("POST", PHOTOBUCKET_HTTPS_REST_SERVER, data_set);
+	oauth_connection_send_message (self->priv->conn,
+				       msg,
+				       cancellable,
+				       callback,
+				       user_data,
+				       photobucket_service_create_album,
+				       create_album_ready_cb,
+				       self);
+
+	g_hash_table_destroy (data_set);
+	*/
+}
+
+
+PhotobucketAlbum *
+photobucket_service_create_album_finish (PhotobucketService  *self,
+				         GAsyncResult        *result,
+				         GError             **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+	else
+		return g_object_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)));
+}
+
+
+/* -- photobucket_service_upload_photos -- */
+
+
+static void
+upload_photos_done (PhotobucketService *self,
+		    GError             *error)
+{
+	GSimpleAsyncResult *result;
+
+	result = oauth_connection_get_result (self->priv->conn);
+	if (error == NULL) {
+		self->priv->post_photos->ids = g_list_reverse (self->priv->post_photos->ids);
+		g_simple_async_result_set_op_res_gpointer (result, self->priv->post_photos->ids, (GDestroyNotify) _g_string_list_free);
+		self->priv->post_photos->ids = NULL;
+	}
+	else {
+		if (self->priv->post_photos->current != NULL) {
+			GthFileData *file_data = self->priv->post_photos->current->data;
+			char        *msg;
+
+			msg = g_strdup_printf (_("Could not upload '%s': %s"), g_file_info_get_display_name (file_data->info), error->message);
+			g_free (error->message);
+			error->message = msg;
+		}
+		g_simple_async_result_set_from_error (result, error);
+	}
+
+	g_simple_async_result_complete_in_idle (result);
+}
+
+
+static void photobucket_service_upload_current_file (PhotobucketService *self);
+
+
+static void
+upload_photo_ready_cb (SoupSession *session,
+		       SoupMessage *msg,
+		       gpointer     user_data)
+{
+	PhotobucketService *self = user_data;
+	GSimpleAsyncResult *result;
+	SoupBuffer         *body;
+	DomDocument        *doc = NULL;
+	GError             *error = NULL;
+	GthFileData        *file_data;
+
+	result = oauth_connection_get_result (self->priv->conn);
+
+	if (msg->status_code != 200) {
+		GError *error;
+
+		error = g_error_new_literal (SOUP_HTTP_ERROR, msg->status_code, soup_status_get_phrase (msg->status_code));
+		upload_photos_done (self, error);
+		g_error_free (error);
+
+		return;
+	}
+
+	body = soup_message_body_flatten (msg->response_body);
+	if (photobucket_utils_parse_response (body, &doc, result, &error)) {
+		DomElement *node;
+
+		/* save the photo id */
+
+		for (node = DOM_ELEMENT (doc)->first_child; node; node = node->next_sibling) {
+			if (g_strcmp0 (node->tag_name, "pid") == 0) {
+				const char *id;
+
+				id = dom_element_get_inner_text (node);
+				self->priv->post_photos->ids = g_list_prepend (self->priv->post_photos->ids, g_strdup (id));
+			}
+		}
+
+		g_object_unref (doc);
+	}
+	else {
+		soup_buffer_free (body);
+		upload_photos_done (self, error);
+		return;
+	}
+
+	soup_buffer_free (body);
+
+	file_data = self->priv->post_photos->current->data;
+	self->priv->post_photos->uploaded_size += g_file_info_get_size (file_data->info);
+	self->priv->post_photos->current = self->priv->post_photos->current->next;
+	photobucket_service_upload_current_file (self);
+}
+
+
+static void
+upload_photo_file_buffer_ready_cb (void     **buffer,
+				   gsize      count,
+				   GError    *error,
+				   gpointer   user_data)
+{
+	PhotobucketService *self = user_data;
+	GthFileData        *file_data;
+	SoupMultipart      *multipart;
+	char               *uri;
+	SoupBuffer         *body;
+	SoupMessage        *msg;
+
+	if (error != NULL) {
+		upload_photos_done (self, error);
+		return;
+	}
+
+	file_data = self->priv->post_photos->current->data;
+	multipart = soup_multipart_new ("multipart/form-data");
+
+	/* the metadata part */
+
+	{
+		GHashTable *data_set;
+		char       *title;
+		char       *description;
+		GList      *keys;
+		GList      *scan;
+
+		data_set = g_hash_table_new (g_str_hash, g_str_equal);
+
+		g_hash_table_insert (data_set, "method", "photobucket.photos.upload");
+
+		title = gth_file_data_get_attribute_as_string (file_data, "general::title");
+		description = gth_file_data_get_attribute_as_string (file_data, "general::description");
+		if (description != NULL)
+			g_hash_table_insert (data_set, "caption", description);
+		else if (title != NULL)
+			g_hash_table_insert (data_set, "caption", title);
+
+		if (self->priv->post_photos->album != NULL)
+			g_hash_table_insert (data_set, "aid", self->priv->post_photos->album->name);
+
+		oauth_connection_add_signature (self->priv->conn, "POST", "FIXME", data_set); /* FIXME */
+
+		keys = g_hash_table_get_keys (data_set);
+		for (scan = keys; scan; scan = scan->next) {
+			char *key = scan->data;
+			soup_multipart_append_form_string (multipart, key, g_hash_table_lookup (data_set, key));
+		}
+
+		g_list_free (keys);
+		g_hash_table_unref (data_set);
+	}
+
+	/* the file part */
+
+	uri = g_file_get_uri (file_data->file);
+	body = soup_buffer_new (SOUP_MEMORY_TEMPORARY, *buffer, count);
+	soup_multipart_append_form_file (multipart,
+					 NULL,
+					 _g_uri_get_basename (uri),
+					 gth_file_data_get_mime_type (file_data),
+					 body);
+
+	soup_buffer_free (body);
+	g_free (uri);
+
+	/* send the file */
+
+	{
+		char *details;
+
+		/* Translators: %s is a filename */
+		details = g_strdup_printf (_("Uploading '%s'"), g_file_info_get_display_name (file_data->info));
+		gth_task_progress (GTH_TASK (self->priv->conn),
+				   NULL, details,
+				   FALSE,
+				   (double) (self->priv->post_photos->uploaded_size + (g_file_info_get_size (file_data->info) / 2.0)) / self->priv->post_photos->total_size);
+
+		g_free (details);
+	}
+
+	/* FIXME
+	msg = soup_form_request_new_from_multipart (PHOTOBUCKET_HTTPS_REST_SERVER, multipart);
+	oauth_connection_send_message (self->priv->conn,
+				       msg,
+				       self->priv->post_photos->cancellable,
+				       self->priv->post_photos->callback,
+				       self->priv->post_photos->user_data,
+				       photobucket_service_upload_photos,
+				       upload_photo_ready_cb,
+				       self);
+	*/
+
+	soup_multipart_free (multipart);
+}
+
+
+static void
+photobucket_service_upload_current_file (PhotobucketService *self)
+{
+	GthFileData *file_data;
+
+	if (self->priv->post_photos->current == NULL) {
+		upload_photos_done (self, NULL);
+		return;
+	}
+
+	file_data = self->priv->post_photos->current->data;
+	g_load_file_async (file_data->file,
+			   G_PRIORITY_DEFAULT,
+			   self->priv->post_photos->cancellable,
+			   upload_photo_file_buffer_ready_cb,
+			   self);
+}
+
+
+static void
+upload_photos_info_ready_cb (GList    *files,
+		             GError   *error,
+		             gpointer  user_data)
+{
+	PhotobucketService *self = user_data;
+	GList              *scan;
+
+	if (error != NULL) {
+		upload_photos_done (self, error);
+		return;
+	}
+
+	self->priv->post_photos->file_list = _g_object_list_ref (files);
+	for (scan = self->priv->post_photos->file_list; scan; scan = scan->next) {
+		GthFileData *file_data = scan->data;
+
+		self->priv->post_photos->total_size += g_file_info_get_size (file_data->info);
+		self->priv->post_photos->n_files += 1;
+	}
+
+	self->priv->post_photos->current = self->priv->post_photos->file_list;
+	photobucket_service_upload_current_file (self);
+}
+
+
+void
+photobucket_service_upload_photos (PhotobucketService  *self,
+				   PhotobucketAlbum    *album,
+				   GList               *file_list, /* GFile list */
+				   GCancellable        *cancellable,
+				   GAsyncReadyCallback  callback,
+				   gpointer             user_data)
+{
+	gth_task_progress (GTH_TASK (self->priv->conn), _("Uploading the files to the server"), NULL, TRUE, 0.0);
+
+	post_photos_data_free (self->priv->post_photos);
+	self->priv->post_photos = g_new0 (PostPhotosData, 1);
+	self->priv->post_photos->album = _g_object_ref (album);
+	self->priv->post_photos->cancellable = _g_object_ref (cancellable);
+	self->priv->post_photos->callback = callback;
+	self->priv->post_photos->user_data = user_data;
+	self->priv->post_photos->total_size = 0;
+	self->priv->post_photos->n_files = 0;
+
+	_g_query_all_metadata_async (file_list,
+				     FALSE,
+				     TRUE,
+				     "*",
+				     self->priv->post_photos->cancellable,
+				     upload_photos_info_ready_cb,
+				     self);
+}
+
+
+GList *
+photobucket_service_upload_photos_finish (PhotobucketService  *self,
+				          GAsyncResult        *result,
+				          GError             **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+	else
+		return _g_string_list_dup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)));
+}
+
+
+#if 0
+
+/* -- photobucket_service_list_photos -- */
+
+
+static void
+list_photos_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 *response;
+		DomElement *node;
+		GList      *photos = NULL;
+
+		response = DOM_ELEMENT (doc)->first_child;
+		for (node = response->first_child; node; node = node->next_sibling) {
+			if (g_strcmp0 (node->tag_name, "photoset") == 0) {
+				DomElement *child;
+				int         position;
+
+				position = 0;
+				for (child = node->first_child; child; child = child->next_sibling) {
+					if (g_strcmp0 (child->tag_name, "photo") == 0) {
+						PhotobucketPhoto *photo;
+
+						photo = photobucket_photo_new ();
+						dom_domizable_load_from_element (DOM_DOMIZABLE (photo), child);
+						photo->position = position++;
+						photos = g_list_prepend (photos, photo);
+					}
+				}
+			}
+		}
+
+		photos = g_list_reverse (photos);
+		g_simple_async_result_set_op_res_gpointer (result, photos, (GDestroyNotify) _g_object_list_unref);
+
+		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_list_photos (PhotobucketService       *self,
+			    PhotobucketPhotoset      *photoset,
+			    const char          *extras,
+			    int                  per_page,
+			    int                  page,
+			    GCancellable        *cancellable,
+			    GAsyncReadyCallback  callback,
+			    gpointer             user_data)
+{
+	GHashTable  *data_set;
+	char        *s;
+	SoupMessage *msg;
+
+	g_return_if_fail (photoset != NULL);
+
+	gth_task_progress (GTH_TASK (self->priv->conn), _("Getting the photo list"), NULL, TRUE, 0.0);
+
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (data_set, "method", "photobucket.photosets.getPhotos");
+	g_hash_table_insert (data_set, "photoset_id", photoset->id);
+	if (extras != NULL)
+		g_hash_table_insert (data_set, "extras", (char *) extras);
+	if (per_page > 0) {
+		s = g_strdup_printf ("%d", per_page);
+		g_hash_table_insert (data_set, "per_page", s);
+		g_free (s);
+	}
+	if (page > 0) {
+		s = g_strdup_printf ("%d", page);
+		g_hash_table_insert (data_set, "page", s);
+		g_free (s);
+	}
+	oauth_connection_add_api_sig (self->priv->conn, data_set);
+	msg = soup_form_request_new_from_hash ("GET", "http://api.photobucket.com/services/rest";, data_set);
+	oauth_connection_send_message (self->priv->conn,
+				       msg,
+				       cancellable,
+				       callback,
+				       user_data,
+				       photobucket_service_list_photos,
+				       list_photos_ready_cb,
+				       self);
+
+	g_hash_table_destroy (data_set);
+}
+
+
+GList *
+photobucket_service_list_photos_finish (PhotobucketService  *self,
+				   GAsyncResult   *result,
+				   GError        **error)
+{
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+	else
+		return _g_object_list_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)));
+}
+
+#endif
diff --git a/extensions/photobucket/photobucket-service.h b/extensions/photobucket/photobucket-service.h
new file mode 100644
index 0000000..fb54680
--- /dev/null
+++ b/extensions/photobucket/photobucket-service.h
@@ -0,0 +1,107 @@
+/* -*- 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>
+#include "photobucket-album.h"
+#include "photobucket-account.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);
+#if 0
+void                 photobucket_service_get_logged_in_user         (PhotobucketService     *self,
+								     GCancellable           *cancellable,
+								     GAsyncReadyCallback     callback,
+								     gpointer                user_data);
+char *               photobucket_service_get_logged_in_user_finish  (PhotobucketService     *self,
+						                     GAsyncResult           *result,
+						                     GError                **error);
+void                 photobucket_service_get_user_info              (PhotobucketService     *self,
+								     const char             *fields,
+								     GCancellable           *cancellable,
+								     GAsyncReadyCallback     callback,
+								     gpointer                user_data);
+PhotobucketUser *    photobucket_service_get_user_info_finish       (PhotobucketService     *self,
+						                     GAsyncResult           *result,
+						                     GError                **error);
+#endif
+void                 photobucket_service_get_albums                 (PhotobucketService     *self,
+								     PhotobucketAccount     *account,
+							             GCancellable           *cancellable,
+							             GAsyncReadyCallback     callback,
+							             gpointer                user_data);
+GList *              photobucket_service_get_albums_finish          (PhotobucketService     *self,
+							             GAsyncResult           *result,
+							             GError                **error);
+void                 photobucket_service_create_album               (PhotobucketService     *self,
+						                     PhotobucketAlbum       *album,
+						                     GCancellable           *cancellable,
+						                     GAsyncReadyCallback     callback,
+						                     gpointer                user_data);
+PhotobucketAlbum *   photobucket_service_create_album_finish        (PhotobucketService     *self,
+						                     GAsyncResult           *result,
+						                     GError                **error);
+void                 photobucket_service_upload_photos              (PhotobucketService     *self,
+							             PhotobucketAlbum       *album,
+							             GList                  *file_list, /* GFile list */
+							             GCancellable           *cancellable,
+							             GAsyncReadyCallback     callback,
+							             gpointer                user_data);
+GList *              photobucket_service_upload_photos_finish       (PhotobucketService     *self,
+						                     GAsyncResult           *result,
+						                     GError                **error);
+#if 0
+void                 photobucket_service_list_photos                (PhotobucketService     *self,
+							             PhotobucketAlbum       *album,
+							             GAsyncReadyCallback     callback,
+							             gpointer                user_data);
+GList *              photobucket_service_list_photos_finish         (PhotobucketService     *self,
+							             GAsyncResult           *result,
+							             GError                **error);
+#endif
+
+#endif /* PHOTOBUCKET_SERVICE_H */
diff --git a/gthumb/gsignature.c b/gthumb/gsignature.c
index bc071f7..8c523ed 100644
--- a/gthumb/gsignature.c
+++ b/gthumb/gsignature.c
@@ -121,7 +121,7 @@ g_signature_free (GSignature *signature)
 
 	g_checksum_free (signature->primary);
 	g_checksum_free (signature->internal);
-	g_free (signature);
+	g_slice_free (GSignature, signature);
 }
 
 
@@ -179,13 +179,14 @@ g_signature_get_value (GSignature *signature,
 
 gchar *
 g_compute_signature_for_data (GChecksumType  checksum_type,
+			      GSignatureEnc  encoding,
 			      const gchar   *key,
 			      gssize         key_length,
 			      const guchar  *data,
 			      gsize          data_length)
 {
 	GSignature *signature;
-	gchar      *retval;
+	gchar      *retval = NULL;
 
 	g_return_val_if_fail (data != NULL, NULL);
 
@@ -194,7 +195,26 @@ g_compute_signature_for_data (GChecksumType  checksum_type,
 		return NULL;
 
 	g_signature_update (signature, data, data_length);
-	retval = g_strdup (g_signature_get_string (signature));
+	switch (encoding) {
+	case G_SIGNATURE_ENC_BASE64:
+		{
+			gsize   buffer_len;
+			guint8 *buffer;
+
+			buffer_len = g_checksum_type_get_length (signature->checksum_type);
+			buffer = g_new (guint8, buffer_len);
+			g_signature_get_value (signature, buffer, &buffer_len);
+			retval = g_base64_encode (buffer, buffer_len);
+
+			g_free (buffer);
+		}
+		break;
+
+	case G_SIGNATURE_ENC_STRING:
+		retval = g_strdup (g_signature_get_string (signature));
+		break;
+	}
+
 	g_signature_free (signature);
 
 	return retval;
@@ -203,6 +223,7 @@ g_compute_signature_for_data (GChecksumType  checksum_type,
 
 gchar *
 g_compute_signature_for_string (GChecksumType  checksum_type,
+				GSignatureEnc  encoding,
 				const gchar   *key,
 				gssize         key_length,
 				const gchar   *str,
@@ -213,5 +234,5 @@ g_compute_signature_for_string (GChecksumType  checksum_type,
 	if (str_length < 0)
 		str_length = strlen (str);
 
-	return g_compute_signature_for_data (checksum_type, key, key_length, (const guchar *) str, str_length);
+	return g_compute_signature_for_data (checksum_type, encoding, key, key_length, (const guchar *) str, str_length);
 }
diff --git a/gthumb/gsignature.h b/gthumb/gsignature.h
index 8c15d2e..32ec94c 100644
--- a/gthumb/gsignature.h
+++ b/gthumb/gsignature.h
@@ -28,6 +28,10 @@
 G_BEGIN_DECLS
 
 typedef struct _GSignature GSignature;
+typedef enum {
+	G_SIGNATURE_ENC_STRING,
+	G_SIGNATURE_ENC_BASE64
+} GSignatureEnc;
 
 GSignature *           g_signature_new         (GChecksumType     checksum_type,
 					        const gchar      *key,
@@ -44,11 +48,13 @@ void                   g_signature_get_value   (GSignature       *signature,
 						gsize            *buffer_len);
 
 gchar *g_compute_signature_for_data   (GChecksumType  checksum_type,
+				       GSignatureEnc  encoding,
 				       const gchar   *key,
 				       gssize         key_length,
                                        const guchar  *data,
                                        gsize          data_length);
 gchar *g_compute_signature_for_string (GChecksumType  checksum_type,
+				       GSignatureEnc  encoding,
 				       const gchar   *key,
 				       gssize         key_length,
                                        const gchar   *str,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index c579236..242c0c3 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,5 +1,5 @@
 if BUILD_TEST_SUITE
-bin_PROGRAMS = dom-test glib-utils-test gsignature_test
+bin_PROGRAMS = dom-test glib-utils-test gsignature-test oauth-test
 endif
 
 dom_test_SOURCES = dom-test.c $(top_srcdir)/gthumb/dom.c
@@ -14,4 +14,8 @@ gsignature_test_SOURCES = gsignature-test.c $(top_srcdir)/gthumb/gsignature.c
 gsignature_test_LDADD = $(GTHUMB_LIBS) 
 gsignature_test_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir)/gthumb
 
+oauth_test_SOURCES = oauth-test.c $(top_srcdir)/gthumb/gsignature.c
+oauth_test_LDADD = $(GTHUMB_LIBS)
+oauth_test_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir)/gthumb
+
 -include $(top_srcdir)/git.mk
diff --git a/tests/gsignature-test.c b/tests/gsignature-test.c
index 2ff4994..bef7bb5 100644
--- a/tests/gsignature-test.c
+++ b/tests/gsignature-test.c
@@ -40,14 +40,14 @@ test_g_signature_md5 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 16; i++)
 		g_string_append (key, "\x0b");
-	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, key->str, -1, "Hi There", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, G_SIGNATURE_ENC_STRING, key->str, -1, "Hi There", -1);
 	g_assert_cmpstr (sig, == , "9294727a3638bb1c13f48ef8158bfc9d");
 	g_free (sig);
 	g_string_free (key, TRUE);
 
 	/* test case 2 */
 
-	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, "Jefe", -1, "what do ya want for nothing?", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, G_SIGNATURE_ENC_STRING, "Jefe", -1, "what do ya want for nothing?", -1);
 	g_assert_cmpstr (sig, == , "750c783e6ab0b503eaa86e310a5db738");
 	g_free (sig);
 
@@ -59,7 +59,7 @@ test_g_signature_md5 (void)
 	data = g_string_new ("");
 	for (i = 0; i < 50; i++)
 		g_string_append (data, "\xdd");
-	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, key->str, -1, data->str, -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, G_SIGNATURE_ENC_STRING, key->str, -1, data->str, -1);
 	g_assert_cmpstr (sig, == , "56be34521d144c88dbb8c733f0e8b3f6");
 	g_free (sig);
 	g_string_free (data, TRUE);
@@ -70,7 +70,7 @@ test_g_signature_md5 (void)
 	data = g_string_new ("");
 	for (i = 0; i < 50; i++)
 		g_string_append (data, "\xcd");
-	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", -1, data->str, -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, G_SIGNATURE_ENC_STRING, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", -1, data->str, -1);
 	g_assert_cmpstr (sig, == , "697eaf0aca3a3aea3a75164746ffaa79");
 	g_free (sig);
 	g_string_free (data, TRUE);
@@ -80,7 +80,7 @@ test_g_signature_md5 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 16; i++)
 		g_string_append (key, "\x0c");
-	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, key->str, -1, "Test With Truncation", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, G_SIGNATURE_ENC_STRING, key->str, -1, "Test With Truncation", -1);
 	g_assert_cmpstr (sig, == , "56461ef2342edc00f9bab995690efd4c");
 	g_free (sig);
 	g_string_free (key, TRUE);
@@ -90,7 +90,7 @@ test_g_signature_md5 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 80; i++)
 		g_string_append (key, "\xaa");
-	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, G_SIGNATURE_ENC_STRING, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
 	g_assert_cmpstr (sig, == , "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd");
 	g_free (sig);
 	g_string_free (key, TRUE);
@@ -100,7 +100,7 @@ test_g_signature_md5 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 80; i++)
 		g_string_append (key, "\xaa");
-	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, key->str, -1, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_MD5, G_SIGNATURE_ENC_STRING, key->str, -1, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", -1);
 	g_assert_cmpstr (sig, == , "6f630fad67cda0ee1fb1f562db3aa53e");
 	g_free (sig);
 	g_string_free (key, TRUE);
@@ -122,14 +122,14 @@ test_g_signature_sha1 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 20; i++)
 		g_string_append (key, "\x0b");
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, key->str, -1, "Hi There", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, key->str, -1, "Hi There", -1);
 	g_assert_cmpstr (sig, == , "b617318655057264e28bc0b6fb378c8ef146be00");
 	g_free (sig);
 	g_string_free (key, TRUE);
 
 	/* test case 2 */
 
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, "Jefe", -1, "what do ya want for nothing?", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, "Jefe", -1, "what do ya want for nothing?", -1);
 	g_assert_cmpstr (sig, == , "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79");
 	g_free (sig);
 
@@ -141,7 +141,7 @@ test_g_signature_sha1 (void)
 	data = g_string_new ("");
 	for (i = 0; i < 50; i++)
 		g_string_append (data, "\xdd");
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, key->str, -1, data->str, -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, key->str, -1, data->str, -1);
 	g_assert_cmpstr (sig, == , "125d7342b9ac11cd91a39af48aa17b4f63f175d3");
 	g_free (sig);
 	g_string_free (data, TRUE);
@@ -152,7 +152,7 @@ test_g_signature_sha1 (void)
 	data = g_string_new ("");
 	for (i = 0; i < 50; i++)
 		g_string_append (data, "\xcd");
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", -1, data->str, -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19", -1, data->str, -1);
 	g_assert_cmpstr (sig, == , "4c9007f4026250c6bc8414f9bf50c86c2d7235da");
 	g_free (sig);
 	g_string_free (data, TRUE);
@@ -162,7 +162,7 @@ test_g_signature_sha1 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 20; i++)
 		g_string_append (key, "\x0c");
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, key->str, -1, "Test With Truncation", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, key->str, -1, "Test With Truncation", -1);
 	g_assert_cmpstr (sig, == , "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04");
 	g_free (sig);
 	g_string_free (key, TRUE);
@@ -172,7 +172,7 @@ test_g_signature_sha1 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 80; i++)
 		g_string_append (key, "\xaa");
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
 	g_assert_cmpstr (sig, == , "aa4ae5e15272d00e95705637ce8a3b55ed402112");
 	g_free (sig);
 	g_string_free (key, TRUE);
@@ -182,27 +182,27 @@ test_g_signature_sha1 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 80; i++)
 		g_string_append (key, "\xaa");
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, key->str, -1, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, key->str, -1, "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", -1);
 	g_assert_cmpstr (sig, == , "e8e99d0f45237d786d6bbaa7965c7808bbff1a91");
 	g_free (sig);
 	g_string_free (key, TRUE);
 
 	/* -- test case taken from php-5.3.2/ext/hash/tests/hash_hmac_bacis.phpt -- */
 
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, "secret", -1, "This is a sample string used to test the hash_hmac function with various hashing algorithms", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, "secret", -1, "This is a sample string used to test the hash_hmac function with various hashing algorithms", -1);
 	g_assert_cmpstr (sig, == , "5bfdb62b97e2c987405463e9f7c193139c0e1fd0");
 	g_free (sig);
 
 	/* -- test case created using the crypto-js library (http://code.google.com/p/crypto-js/) -- */
 
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, "Secret Passphrase", -1, "Message", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, "Secret Passphrase", -1, "Message", -1);
 	g_assert_cmpstr (sig, == , "e90f713295ea4cc06c92c9248696ffafc5d01faf");
 	g_free (sig);
 
 	key = g_string_new ("");
 	for (i = 0; i < 1; i++)
 		g_string_append (key, "aa");
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
 	g_assert_cmpstr (sig, == , "4f8e4da5b85182a352041a22b4d566b4b53abf9e");
 	g_free (sig);
 	g_string_free (key, TRUE);
@@ -210,7 +210,7 @@ test_g_signature_sha1 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 20; i++)
 		g_string_append (key, "aa");
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
 	g_assert_cmpstr (sig, == , "cb8da9d75b1cd177c00e8c46bd9c17fa313b9f6c");
 	g_free (sig);
 	g_string_free (key, TRUE);
@@ -218,7 +218,7 @@ test_g_signature_sha1 (void)
 	key = g_string_new ("");
 	for (i = 0; i < 80; i++)
 		g_string_append (key, "aa");
-	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
+	sig = g_compute_signature_for_string (G_CHECKSUM_SHA1, G_SIGNATURE_ENC_STRING, key->str, -1, "Test Using Larger Than Block-Size Key - Hash Key First", -1);
 	g_assert_cmpstr (sig, == , "f196c43f06a566cb096a72227a3196d97236898b");
 	g_free (sig);
 	g_string_free (key, TRUE);
diff --git a/tests/oauth-test.c b/tests/oauth-test.c
new file mode 100644
index 0000000..2c1033c
--- /dev/null
+++ b/tests/oauth-test.c
@@ -0,0 +1,133 @@
+/* -*- 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 <string.h>
+#include "gsignature.h"
+
+
+static void
+oauth_connection_add_signature (const char *method,
+				const char *url,
+			        GHashTable *parameters,
+			        const char *expected)
+{
+	char     *timestamp;
+	char     *nonce;
+	char     *consumer_key;
+	char     *consumer_secret;
+	GString  *param_string;
+	GList    *keys;
+	GList    *scan;
+	GString  *base_string;
+	GString  *signature_key;
+	char     *signature;
+
+	/* example values from photobucket */
+
+	timestamp = "1208289833";
+	nonce = "cc09413d20742699147d083d755023f7";
+	consumer_key = "1020304";
+	consumer_secret = "9eb84090956c484e32cb6c08455a667b";
+
+	/* Add the OAuth specific parameters */
+
+	g_hash_table_insert (parameters, "oauth_timestamp", timestamp);
+	g_hash_table_insert (parameters, "oauth_nonce", nonce);
+	g_hash_table_insert (parameters, "oauth_version", "1.0");
+	g_hash_table_insert (parameters, "oauth_signature_method", "HMAC-SHA1");
+	g_hash_table_insert (parameters, "oauth_consumer_key", consumer_key);
+
+	/* Create the 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, consumer_secret);
+	g_string_append (signature_key, "&");
+	signature = g_compute_signature_for_string (G_CHECKSUM_SHA1,
+						    G_SIGNATURE_ENC_BASE64,
+						    signature_key->str,
+						    signature_key->len,
+						    base_string->str,
+						    base_string->len);
+	g_hash_table_insert (parameters, "oauth_signature", signature);
+
+	g_assert_cmpstr (signature, == , expected);
+
+	g_free (signature);
+	g_string_free (signature_key, TRUE);
+	g_string_free (base_string, TRUE);
+	g_list_free (keys);
+	g_string_free (param_string, TRUE);
+}
+
+
+static void
+test_oauth_signature (void)
+{
+	GHashTable  *data_set;
+
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+
+	oauth_connection_add_signature ("GET",
+					"http://api.photobucket.com/ping";,
+					data_set,
+				        "2j4y6ocB4d4tTxDoHaNulKpA46c=");
+
+	g_hash_table_destroy (data_set);
+}
+
+
+int
+main (int   argc,
+      char *argv[])
+{
+	g_test_init (&argc, &argv, NULL);
+	g_test_add_func ("/oauth/signature", test_oauth_signature);
+	return g_test_run ();
+}



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