[gthumb] facebook: moved some code to a generic WebService class



commit f13f9079260645771fb15e849379e599cb5bf1fa
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Sat Dec 15 16:57:01 2012 +0100

    facebook: moved some code to a generic WebService class

 extensions/facebook/Makefile.am                    |    6 -
 extensions/facebook/dlg-export-to-facebook.c       |   75 +--
 extensions/facebook/facebook-authentication.c      |  619 --------------
 extensions/facebook/facebook-authentication.h      |   78 --
 extensions/facebook/facebook-connection.c          |  329 --------
 extensions/facebook/facebook-connection.h          |   94 ---
 extensions/facebook/facebook-service.c             |  438 ++++++++---
 extensions/facebook/facebook-service.h             |   21 +-
 extensions/facebook/facebook-user.c                |  173 ----
 extensions/facebook/facebook-user.h                |   58 --
 extensions/oauth/Makefile.am                       |    4 +-
 extensions/oauth/oauth-account.c                   |    8 +-
 extensions/oauth/oauth.h                           |    1 +
 extensions/oauth/oauth2-ask-authorization-dialog.c |    5 +-
 extensions/oauth/oauth2-ask-authorization-dialog.h |    3 +-
 extensions/oauth/web-service.c                     |  852 ++++++++++++++++++++
 extensions/oauth/web-service.h                     |  111 +++
 17 files changed, 1332 insertions(+), 1543 deletions(-)
---
diff --git a/extensions/facebook/Makefile.am b/extensions/facebook/Makefile.am
index 3795a91..3fb505a 100644
--- a/extensions/facebook/Makefile.am
+++ b/extensions/facebook/Makefile.am
@@ -16,17 +16,11 @@ libfacebook_la_SOURCES = 			\
 	facebook-album.h			\
 	facebook-album-properties-dialog.c	\
 	facebook-album-properties-dialog.h	\
-	facebook-authentication.c		\
-	facebook-authentication.h		\
-	facebook-connection.c			\
-	facebook-connection.h			\
 	facebook-photo.c			\
 	facebook-photo.h			\
 	facebook-service.c			\
 	facebook-service.h			\
 	facebook-types.h			\
-	facebook-user.c				\
-	facebook-user.h				\
 	main.c					\
 	preferences.h
 
diff --git a/extensions/facebook/dlg-export-to-facebook.c b/extensions/facebook/dlg-export-to-facebook.c
index 855689d..1ec8d54 100644
--- a/extensions/facebook/dlg-export-to-facebook.c
+++ b/extensions/facebook/dlg-export-to-facebook.c
@@ -24,11 +24,9 @@
 #include <gthumb.h>
 #include <extensions/oauth/oauth.h>
 #include "dlg-export-to-facebook.h"
-#include "facebook-authentication.h"
 #include "facebook-album.h"
 #include "facebook-album-properties-dialog.h"
 #include "facebook-service.h"
-#include "facebook-user.h"
 #include "preferences.h"
 
 
@@ -65,10 +63,7 @@ typedef struct {
 	GtkWidget              *dialog;
 	GtkWidget              *list_view;
 	GtkWidget              *progress_dialog;
-	FacebookConnection     *conn;
-	FacebookAuthentication *auth;
 	FacebookService        *service;
-	OAuthAccount           *account;
 	GList                  *albums;
 	FacebookAlbum          *album;
 	GList                  *photos_ids;
@@ -81,16 +76,13 @@ destroy_dialog (DialogData *data)
 {
 	if (data->dialog != NULL)
 		gtk_widget_destroy (data->dialog);
-	if (data->conn != NULL)
-		gth_task_completed (GTH_TASK (data->conn), NULL);
+	if (data->service != NULL)
+		gth_task_completed (GTH_TASK (data->service), NULL);
 	_g_object_unref (data->cancellable);
 	_g_string_list_free (data->photos_ids);
 	_g_object_unref (data->album);
 	_g_object_list_unref (data->albums);
-	_g_object_unref (data->account);
 	_g_object_unref (data->service);
-	_g_object_unref (data->auth);
-	_g_object_unref (data->conn);
 	_g_object_unref (data->settings);
 	_g_object_unref (data->builder);
 	_g_object_list_unref (data->file_list);
@@ -126,8 +118,7 @@ completed_messagedialog_response_cb (GtkDialog *dialog,
 				url = g_strdup (data->album->link);
 
 			if ((url != NULL) && ! gtk_show_uri (screen, url, 0, &error)) {
-				if (data->conn != NULL)
-					gth_task_dialog (GTH_TASK (data->conn), TRUE, NULL);
+				gth_task_dialog (GTH_TASK (data->service), TRUE, NULL);
 				_gtk_error_dialog_from_gerror_run (GTK_WINDOW (data->browser), _("Could not connect to the server"), error);
 				g_clear_error (&error);
 			}
@@ -150,7 +141,7 @@ export_completed_with_success (DialogData *data)
 	GtkBuilder *builder;
 	GtkWidget  *dialog;
 
-	gth_task_dialog (GTH_TASK (data->conn), TRUE, NULL);
+	gth_task_dialog (GTH_TASK (data->service), TRUE, NULL);
 
 	builder = _gtk_builder_new_from_file ("facebook-export-completed.ui", "facebook");
 	dialog = _gtk_builder_get_widget (builder, "completed_messagedialog");
@@ -221,7 +212,7 @@ export_dialog_response_cb (GtkDialog *dialog,
 				return;
 
 			gtk_widget_hide (data->dialog);
-			gth_task_dialog (GTH_TASK (data->conn), FALSE, NULL);
+			gth_task_dialog (GTH_TASK (data->service), FALSE, NULL);
 
 			max_resolution = 0;
 			if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (GET_WIDGET ("resize_combobox")), &iter)) {
@@ -264,8 +255,8 @@ update_account_list (DialogData *data)
 	gtk_list_store_clear (GTK_LIST_STORE (GET_WIDGET ("account_liststore")));
 
 	current_account_idx = 0;
-	current_account = facebook_authentication_get_account (data->auth);
-	for (scan = facebook_authentication_get_accounts (data->auth), idx = 0; scan; scan = scan->next, idx++) {
+	current_account = web_service_get_current_account (WEB_SERVICE (data->service));
+	for (scan = web_service_get_accounts (WEB_SERVICE (data->service)), idx = 0; scan; scan = scan->next, idx++) {
 		OAuthAccount *account = scan->data;
 
 		if ((current_account != NULL) && (g_strcmp0 (current_account->username, account->username) == 0))
@@ -282,8 +273,8 @@ update_account_list (DialogData *data)
 
 
 static void
-authentication_accounts_changed_cb (FacebookAuthentication *auth,
-				    gpointer              user_data)
+service_accounts_changed_cb (FacebookService *service,
+			     gpointer         user_data)
 {
 	update_account_list ((DialogData *) user_data);
 }
@@ -330,8 +321,7 @@ get_albums_ready_cb (GObject      *source_object,
 	_g_object_list_unref (data->albums);
 	data->albums = facebook_service_get_albums_finish (data->service, res, &error);
 	if (error != NULL) {
-		if (data->conn != NULL)
-			gth_task_dialog (GTH_TASK (data->conn), TRUE, NULL);
+		gth_task_dialog (GTH_TASK (data->service), TRUE, NULL);
 		_gtk_error_dialog_from_gerror_run (GTK_WINDOW (data->browser), _("Could not connect to the server"), error);
 		g_clear_error (&error);
 		gtk_dialog_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_DELETE_EVENT);
@@ -340,7 +330,7 @@ get_albums_ready_cb (GObject      *source_object,
 
 	update_album_list (data, NULL);
 
-	gth_task_dialog (GTH_TASK (data->conn), TRUE, NULL);
+	gth_task_dialog (GTH_TASK (data->service), TRUE, NULL);
 	gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (data->browser));
 	gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
 	gtk_window_present (GTK_WINDOW (data->dialog));
@@ -348,13 +338,10 @@ get_albums_ready_cb (GObject      *source_object,
 
 
 static void
-authentication_ready_cb (FacebookAuthentication *auth,
-			 DialogData             *data)
+service_account_ready_cb (FacebookService *service,
+			  DialogData      *data)
 {
-	_g_object_unref (data->account);
-	data->account = _g_object_ref (facebook_authentication_get_account (auth));
 	update_account_list (data);
-
 	facebook_service_get_albums (data->service,
 				     data->cancellable,
 				     get_albums_ready_cb,
@@ -366,7 +353,7 @@ static void
 edit_accounts_button_clicked_cb (GtkButton  *button,
 				 DialogData *data)
 {
-	facebook_authentication_edit_accounts (data->auth, GTK_WINDOW (data->dialog));
+	web_service_edit_accounts (WEB_SERVICE (data->service), GTK_WINDOW (data->dialog));
 }
 
 
@@ -386,8 +373,8 @@ account_combobox_changed_cb (GtkComboBox *widget,
 			    ACCOUNT_DATA_COLUMN, &account,
 			    -1);
 
-	if (oauth_account_cmp (account, facebook_authentication_get_account (data->auth)) != 0)
-		facebook_authentication_connect (data->auth, account);
+	if (oauth_account_cmp (account, web_service_get_current_account (WEB_SERVICE (data->service))) != 0)
+		web_service_connect (WEB_SERVICE (data->service), account);
 
 	g_object_unref (account);
 }
@@ -404,8 +391,8 @@ create_album_ready_cb (GObject      *source_object,
 
 	album = facebook_service_create_album_finish (data->service, result, &error);
 	if (error != NULL) {
-		if (data->conn != NULL)
-			gth_task_dialog (GTH_TASK (data->conn), TRUE, NULL);
+		if (data->service != NULL)
+			gth_task_dialog (GTH_TASK (data->service), TRUE, NULL);
 		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->browser), _("Could not create the album"), error);
 		g_clear_error (&error);
 		return;
@@ -655,24 +642,20 @@ dlg_export_to_facebook (GthBrowser *browser,
 			  G_CALLBACK (album_combobox_changed_cb),
 			  data);
 
-	data->conn = facebook_connection_new ();
-	data->service = facebook_service_new (data->conn);
-	data->auth = facebook_authentication_new (data->conn,
-						  data->service,
-						  data->cancellable,
-						  GTK_WIDGET (data->browser),
-						  data->dialog);
-	g_signal_connect (data->auth,
-			  "ready",
-			  G_CALLBACK (authentication_ready_cb),
+	data->service = facebook_service_new (data->cancellable,
+					      GTK_WIDGET (data->browser),
+					      data->dialog);
+	g_signal_connect (data->service,
+			  "account-ready",
+			  G_CALLBACK (service_account_ready_cb),
 			  data);
-	g_signal_connect (data->auth,
-			  "accounts_changed",
-			  G_CALLBACK (authentication_accounts_changed_cb),
+	g_signal_connect (data->service,
+			  "accounts-changed",
+			  G_CALLBACK (service_accounts_changed_cb),
 			  data);
 
 	data->progress_dialog = gth_progress_dialog_new (GTK_WINDOW (data->browser));
-	gth_progress_dialog_add_task (GTH_PROGRESS_DIALOG (data->progress_dialog), GTH_TASK (data->conn));
+	gth_progress_dialog_add_task (GTH_PROGRESS_DIALOG (data->progress_dialog), GTH_TASK (data->service));
 
-	facebook_authentication_auto_connect (data->auth);
+	web_service_autoconnect (WEB_SERVICE (data->service));
 }
diff --git a/extensions/facebook/facebook-service.c b/extensions/facebook/facebook-service.c
index 2b4c4bb..c5ae554 100644
--- a/extensions/facebook/facebook-service.c
+++ b/extensions/facebook/facebook-service.c
@@ -26,13 +26,23 @@
 #include <gthumb.h>
 #include <extensions/oauth/oauth.h>
 #include "facebook-album.h"
-#include "facebook-connection.h"
 #include "facebook-photo.h"
 #include "facebook-service.h"
-#include "facebook-user.h"
 
-#define FACEBOOK_MIN_IMAGE_SIZE 720
+
+#define FACEBOOK_API_VERSION "1.0"
+#define FACEBOOK_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT 2
+#define FACEBOOK_HTTP_SERVER "https://www.facebook.com";
 #define FACEBOOK_MAX_IMAGE_SIZE 2048
+#define FACEBOOK_MIN_IMAGE_SIZE 720
+#define FACEBOOK_REDIRECT_URI "https://www.facebook.com/connect/login_success.html";
+#define FACEBOOK_SERVICE_ERROR_TOKEN_EXPIRED 190
+#define GTHUMB_FACEBOOK_API_KEY "1536ca726857c69843423d0312b9b356"
+#define GTHUMB_FACEBOOK_SHARED_SECRET "8c0b99672a9bbc159ebec3c9a8240679"
+
+
+/* -- post_photos_data -- */
+
 
 typedef struct {
 	FacebookAlbum       *album;
@@ -64,63 +74,211 @@ post_photos_data_free (PostPhotosData *post_photos)
 }
 
 
-struct _FacebookServicePrivate
-{
-	FacebookConnection *conn;
-	FacebookUser       *user;
-	PostPhotosData     *post_photos;
-};
+/* -- facebook_service -- */
 
 
-G_DEFINE_TYPE (FacebookService, facebook_service, G_TYPE_OBJECT)
+G_DEFINE_TYPE (FacebookService, facebook_service, WEB_TYPE_SERVICE)
+
+
+struct _FacebookServicePrivate {
+	char           *token;
+	PostPhotosData *post_photos;
+};
 
 
 static void
 facebook_service_finalize (GObject *object)
 {
-	FacebookService *self;
-
-	self = FACEBOOK_SERVICE (object);
+	FacebookService *self = FACEBOOK_SERVICE (object);
 
-	_g_object_unref (self->priv->conn);
-	_g_object_unref (self->priv->user);
 	post_photos_data_free (self->priv->post_photos);
+	g_free (self->priv->token);
 
 	G_OBJECT_CLASS (facebook_service_parent_class)->finalize (object);
 }
 
 
+/* -- connection utilities -- */
+
+
 static void
-facebook_service_class_init (FacebookServiceClass *klass)
+_facebook_service_set_access_token (FacebookService *self,
+				    const char      *token)
 {
-	GObjectClass *object_class;
+	_g_strset (&self->priv->token, token);
+}
 
-	g_type_class_add_private (klass, sizeof (FacebookServicePrivate));
 
-	object_class = (GObjectClass*) klass;
-	object_class->finalize = facebook_service_finalize;
+static const char *
+_facebook_service_get_access_token (FacebookService *self)
+{
+	return self->priv->token;
 }
 
 
 static void
-facebook_service_init (FacebookService *self)
+_facebook_service_add_access_token (FacebookService *self,
+				    GHashTable      *data_set)
 {
-	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, FACEBOOK_TYPE_SERVICE, FacebookServicePrivate);
-	self->priv->conn = NULL;
-	self->priv->user = NULL;
-	self->priv->post_photos = NULL;
+	g_return_if_fail (self->priv->token != NULL);
+
+	g_hash_table_insert (data_set, "access_token", self->priv->token);
 }
 
 
-FacebookService *
-facebook_service_new (FacebookConnection *conn)
+static char *
+get_access_type_name (WebAuthorizationType access_type)
+{
+	char *name = NULL;
+
+	switch (access_type) {
+	case WEB_AUTHORIZATION_READ:
+		name = "";
+		break;
+
+	case WEB_AUTHORIZATION_WRITE:
+		name = "publish_actions";
+		break;
+	}
+
+	return name;
+}
+
+
+static char *
+facebook_utils_get_authorization_url (WebAuthorizationType access_type)
 {
-	FacebookService *self;
+	GHashTable *data_set;
+	GString    *link;
+	GList      *keys;
+	GList      *scan;
+
+	data_set = g_hash_table_new (g_str_hash, g_str_equal);
+	g_hash_table_insert (data_set, "client_id", GTHUMB_FACEBOOK_API_KEY);
+	g_hash_table_insert (data_set, "redirect_uri", FACEBOOK_REDIRECT_URI);
+	g_hash_table_insert (data_set, "scope", get_access_type_name (access_type));
+	g_hash_table_insert (data_set, "response_type", "token");
+
+	link = g_string_new ("https://www.facebook.com/dialog/oauth?";);
+	keys = g_hash_table_get_keys (data_set);
+	for (scan = keys; scan; scan = scan->next) {
+		char *key = scan->data;
+		char *encoded;
+
+		if (scan != keys)
+			g_string_append (link, "&");
+		g_string_append (link, key);
+		g_string_append (link, "=");
+		encoded = soup_uri_encode (g_hash_table_lookup (data_set, key), NULL);
+		g_string_append (link, encoded);
+
+		g_free (encoded);
+	}
 
-	self = (FacebookService *) g_object_new (FACEBOOK_TYPE_SERVICE, NULL);
-	self->priv->conn = _g_object_ref (conn);
+	g_list_free (keys);
+	g_hash_table_destroy (data_set);
 
-	return self;
+	return g_string_free (link, FALSE);
+}
+
+
+static gboolean
+facebook_utils_parse_response (SoupMessage  *msg,
+			       JsonNode    **node,
+			       GError      **error)
+{
+	JsonParser *parser;
+	SoupBuffer *body;
+
+	g_return_val_if_fail (msg != NULL, FALSE);
+	g_return_val_if_fail (node != NULL, FALSE);
+
+	*node = NULL;
+
+	if ((msg->status_code != 200) && (msg->status_code != 400)) {
+		*error = g_error_new (SOUP_HTTP_ERROR,
+				      msg->status_code,
+				      "%s",
+				      soup_status_get_phrase (msg->status_code));
+		return FALSE;
+	}
+
+	body = soup_message_body_flatten (msg->response_body);
+	parser = json_parser_new ();
+	if (json_parser_load_from_data (parser, body->data, body->length, error)) {
+		JsonObject *obj;
+
+		*node = json_node_copy (json_parser_get_root (parser));
+
+		obj = json_node_get_object (*node);
+		if (json_object_has_member (obj, "error")) {
+			JsonObject *error_obj;
+
+			error_obj = json_object_get_object_member (obj, "error");
+			*error = g_error_new (WEB_SERVICE_ERROR,
+					      json_object_get_int_member (error_obj, "code"),
+					      "%s",
+					      json_object_get_string_member (error_obj, "message"));
+
+			json_node_free (*node);
+			*node = NULL;
+		}
+	}
+
+	g_object_unref (parser);
+	soup_buffer_free (body);
+
+	return *node != NULL;
+}
+
+
+/* -- facebook_service_ask_authorization -- */
+
+
+static void
+ask_authorization_dialog_redirected_cb (OAuth2AskAuthorizationDialog *dialog,
+					gpointer                      user_data)
+{
+	FacebookService *self = user_data;
+	const char      *uri;
+
+	uri = oauth2_ask_authorization_dialog_get_uri (dialog);
+	if (g_str_has_prefix (uri, FACEBOOK_REDIRECT_URI)) {
+		const char *uri_data;
+		GHashTable *data;
+		const char *access_token;
+
+		uri_data = uri + strlen (FACEBOOK_REDIRECT_URI "#");
+
+		data = soup_form_decode (uri_data);
+		access_token = g_hash_table_lookup (data, "access_token");
+		_facebook_service_set_access_token (self, access_token);
+		gtk_dialog_response (GTK_DIALOG (dialog),
+				     (access_token != NULL) ? GTK_RESPONSE_OK : GTK_RESPONSE_CANCEL);
+
+		g_hash_table_destroy (data);
+	}
+}
+
+
+static void
+facebook_service_ask_authorization (WebService *base)
+{
+	FacebookService *self = FACEBOOK_SERVICE (base);
+	GtkWidget       *dialog;
+
+	gth_task_dialog (GTH_TASK (self), TRUE, NULL);
+
+	dialog = oauth2_ask_authorization_dialog_new (facebook_utils_get_authorization_url (WEB_AUTHORIZATION_WRITE));
+	gtk_window_set_default_size (GTK_WINDOW (dialog), 800, 600);
+	_web_service_set_auth_dialog (WEB_SERVICE (self), GTK_DIALOG (dialog));
+
+	g_signal_connect (OAUTH2_ASK_AUTHORIZATION_DIALOG (dialog),
+			  "redirected",
+			  G_CALLBACK (ask_authorization_dialog_redirected_cb),
+			  self);
+
+	gtk_widget_show (dialog);
 }
 
 
@@ -128,24 +286,32 @@ facebook_service_new (FacebookConnection *conn)
 
 
 static void
-facebook_service_get_user_ready_cb (SoupSession *session,
-				    SoupMessage *msg,
-				    gpointer     user_data)
+facebook_service_get_user_info_ready_cb (SoupSession *session,
+				         SoupMessage *msg,
+				         gpointer     user_data)
 {
 	FacebookService    *self = user_data;
 	GSimpleAsyncResult *result;
 	GError             *error = NULL;
 	JsonNode           *node;
 
-	result = facebook_connection_get_result (self->priv->conn);
+	result = _web_service_get_result (WEB_SERVICE (self));
 
 	if (facebook_utils_parse_response (msg, &node, &error)) {
-		_g_object_unref (self->priv->user);
-		self->priv->user = (FacebookUser *) json_gobject_deserialize (FACEBOOK_TYPE_USER, node);
+		OAuthAccount *account;
+
+		account = (OAuthAccount *) json_gobject_deserialize (OAUTH_TYPE_ACCOUNT, node);
+		g_object_set (account,
+			      "token", _facebook_service_get_access_token (self),
+			      "token-secret", _facebook_service_get_access_token (self),
+			      NULL);
+		web_service_set_current_account (WEB_SERVICE (self), account);
+
 		g_simple_async_result_set_op_res_gpointer (result,
-							   g_object_ref (self->priv->user),
+							   g_object_ref (account),
 							   (GDestroyNotify) g_object_unref);
 
+		_g_object_unref (account);
 		json_node_free (node);
 	}
 	else
@@ -155,46 +321,75 @@ facebook_service_get_user_ready_cb (SoupSession *session,
 }
 
 
-void
-facebook_service_get_user (FacebookService     *self,
-			   GCancellable        *cancellable,
-			   GAsyncReadyCallback  callback,
-			   gpointer	        user_data)
+static void
+facebook_service_get_user_info (WebService          *base,
+				GCancellable        *cancellable,
+				GAsyncReadyCallback  callback,
+				gpointer	     user_data)
 {
-	GHashTable  *data_set;
-	SoupMessage *msg;
+	FacebookService *self = FACEBOOK_SERVICE (base);
+	OAuthAccount    *account;
+	GHashTable      *data_set;
+	SoupMessage     *msg;
 
-	gth_task_progress (GTH_TASK (self->priv->conn),
-			   _("Connecting to the server"),
-			   _("Getting account information"),
-			   TRUE,
-			   0.0);
+	account = web_service_get_current_account (WEB_SERVICE (self));
+	_facebook_service_set_access_token (self, account->token_secret);
 
 	data_set = g_hash_table_new (g_str_hash, g_str_equal);
-	facebook_connection_add_access_token (self->priv->conn, data_set);
+	_facebook_service_add_access_token (self, data_set);
 	msg = soup_form_request_new_from_hash ("GET", "https://graph.facebook.com/me";, data_set);
-	facebook_connection_send_message (self->priv->conn,
-					  msg,
-					  cancellable,
-					  callback,
-					  user_data,
-					  facebook_service_get_user,
-					  facebook_service_get_user_ready_cb,
-					  self);
+	_web_service_send_message (WEB_SERVICE (self),
+				   msg,
+				   cancellable,
+				   callback,
+				   user_data,
+				   facebook_service_get_user_info,
+				   facebook_service_get_user_info_ready_cb,
+				   self);
 
 	g_hash_table_destroy (data_set);
 }
 
 
-FacebookUser *
-facebook_service_get_user_finish (FacebookService  *self,
-				  GAsyncResult     *result,
-				  GError          **error)
+static void
+facebook_service_class_init (FacebookServiceClass *klass)
 {
-	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)));
+	GObjectClass    *object_class;
+	WebServiceClass *service_class;
+
+	g_type_class_add_private (klass, sizeof (FacebookServicePrivate));
+
+	object_class = (GObjectClass*) klass;
+	object_class->finalize = facebook_service_finalize;
+
+	service_class = (WebServiceClass*) klass;
+	service_class->ask_authorization = facebook_service_ask_authorization;
+	service_class->get_user_info = facebook_service_get_user_info;
+}
+
+
+static void
+facebook_service_init (FacebookService *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, FACEBOOK_TYPE_SERVICE, FacebookServicePrivate);
+	self->priv->token = NULL;
+	self->priv->post_photos = NULL;
+}
+
+
+FacebookService *
+facebook_service_new (GCancellable *cancellable,
+		      GtkWidget    *browser,
+		      GtkWidget    *dialog)
+{
+	return g_object_new (FACEBOOK_TYPE_SERVICE,
+			     "service-name", "facebook",
+			     "service-address", "https://www.facebook.com";,
+			     "service-protocol", "https",
+			     "cancellable", cancellable,
+			     "browser", browser,
+			     "dialog", dialog,
+			     NULL);
 }
 
 
@@ -211,7 +406,7 @@ facebook_service_get_albums_ready_cb (SoupSession *session,
 	JsonNode           *node;
 	GError             *error = NULL;
 
-	result = facebook_connection_get_result (self->priv->conn);
+	result = _web_service_get_result (WEB_SERVICE (self));
 
 	if (facebook_utils_parse_response (msg, &node, &error)) {
 		GList      *albums = NULL;
@@ -248,30 +443,32 @@ facebook_service_get_albums (FacebookService     *self,
 			     GAsyncReadyCallback  callback,
 			     gpointer             user_data)
 {
-	GHashTable  *data_set;
-	char        *uri;
-	SoupMessage *msg;
+	OAuthAccount *account;
+	GHashTable   *data_set;
+	char         *uri;
+	SoupMessage  *msg;
 
-	g_return_if_fail (self->priv->user != NULL);
+	account = web_service_get_current_account (WEB_SERVICE (self));
+	g_return_if_fail (account != NULL);
 
-	gth_task_progress (GTH_TASK (self->priv->conn),
+	gth_task_progress (GTH_TASK (self),
 			   _("Getting the album list"),
 			   NULL,
 			   TRUE,
 			   0.0);
 
-	uri = g_strdup_printf ("https://graph.facebook.com/%s/albums";, self->priv->user->id);
+	uri = g_strdup_printf ("https://graph.facebook.com/%s/albums";, account->id);
 	data_set = g_hash_table_new (g_str_hash, g_str_equal);
-	facebook_connection_add_access_token (self->priv->conn, data_set);
+	_facebook_service_add_access_token (self, data_set);
 	msg = soup_form_request_new_from_hash ("GET", uri, data_set);
-	facebook_connection_send_message (self->priv->conn,
-					  msg,
-					  cancellable,
-					  callback,
-					  user_data,
-					  facebook_service_get_albums,
-					  facebook_service_get_albums_ready_cb,
-					  self);
+	_web_service_send_message (WEB_SERVICE (self),
+				   msg,
+				   cancellable,
+				   callback,
+				   user_data,
+				   facebook_service_get_albums,
+				   facebook_service_get_albums_ready_cb,
+				   self);
 
 	g_free (uri);
 	g_hash_table_destroy (data_set);
@@ -333,7 +530,7 @@ facebook_service_create_album_ready_cb (SoupSession *session,
 	JsonNode           *node;
 	GError             *error = NULL;
 
-	result = facebook_connection_get_result (self->priv->conn);
+	result = _web_service_get_result (WEB_SERVICE (self));
 
 	if (facebook_utils_parse_response (msg, &node, &error)) {
 		FacebookAlbum *album;
@@ -364,36 +561,43 @@ facebook_service_create_album (FacebookService     *self,
 			       GAsyncReadyCallback  callback,
 			       gpointer             user_data)
 {
+	OAuthAccount    *account;
 	CreateAlbumData *ca_data;
 	char            *uri;
 	GHashTable      *data_set;
 	SoupMessage     *msg;
 
-	g_return_if_fail (self->priv->user != NULL);
+	account = web_service_get_current_account (WEB_SERVICE (self));
+	g_return_if_fail (account != NULL);
+
 	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);
+	gth_task_progress (GTH_TASK (self),
+			   _("Creating the new album"),
+			   NULL,
+			   TRUE,
+			   0.0);
 
 	ca_data = create_album_data_new (self, album);
 
-	uri = g_strdup_printf ("https://graph.facebook.com/%s/albums";, self->priv->user->id);
+	uri = g_strdup_printf ("https://graph.facebook.com/%s/albums";, account->id);
 	data_set = g_hash_table_new (g_str_hash, g_str_equal);
 	g_hash_table_insert (data_set, "name", album->name);
 	if (album->description != NULL)
 		g_hash_table_insert (data_set, "message", album->description);
 	if (album->privacy != NULL)
 		g_hash_table_insert (data_set, "privacy", album->privacy);
-	facebook_connection_add_access_token (self->priv->conn, data_set);
+	_facebook_service_add_access_token (self, data_set);
 	msg = soup_form_request_new_from_hash ("POST", uri, data_set);
-	facebook_connection_send_message (self->priv->conn,
-					  msg,
-					  cancellable,
-					  callback,
-					  user_data,
-					  facebook_service_create_album,
-					  facebook_service_create_album_ready_cb,
-					  ca_data);
+	_web_service_send_message (WEB_SERVICE (self),
+				   msg,
+				   cancellable,
+				   callback,
+				   user_data,
+				   facebook_service_create_album,
+				   facebook_service_create_album_ready_cb,
+				   ca_data);
 
 	g_hash_table_destroy (data_set);
 }
@@ -420,7 +624,7 @@ upload_photos_done (FacebookService *self,
 {
 	GSimpleAsyncResult *result;
 
-	result = facebook_connection_get_result (self->priv->conn);
+	result = _web_service_get_result (WEB_SERVICE (self));
 	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);
@@ -498,7 +702,7 @@ upload_photo_wrote_body_data_cb (SoupMessage *msg,
 	/* Translators: %s is a filename */
 	details = g_strdup_printf (_("Uploading '%s'"), g_file_info_get_display_name (file_data->info));
 	current_file_fraction = (double) self->priv->post_photos->wrote_body_data_size / msg->request_body->length;
-	gth_task_progress (GTH_TASK (self->priv->conn),
+	gth_task_progress (GTH_TASK (self),
 			   NULL,
 			   details,
 			   FALSE,
@@ -547,7 +751,7 @@ upload_photo_file_buffer_ready_cb (void     **buffer,
 		else if (title != NULL)
 			g_hash_table_insert (data_set, "message", title);
 
-		facebook_connection_add_access_token (self->priv->conn, data_set);
+		_facebook_service_add_access_token (self, data_set);
 
 		keys = g_hash_table_get_keys (data_set);
 		for (scan = keys; scan; scan = scan->next) {
@@ -639,14 +843,14 @@ upload_photo_file_buffer_ready_cb (void     **buffer,
 			  "wrote-body-data",
 			  (GCallback) upload_photo_wrote_body_data_cb,
 			  self);
-	facebook_connection_send_message (self->priv->conn,
-					  msg,
-					  self->priv->post_photos->cancellable,
-					  self->priv->post_photos->callback,
-					  self->priv->post_photos->user_data,
-					  facebook_service_upload_photos,
-					  upload_photo_ready_cb,
-					  self);
+	_web_service_send_message (WEB_SERVICE (self),
+				   msg,
+				   self->priv->post_photos->cancellable,
+				   self->priv->post_photos->callback,
+				   self->priv->post_photos->user_data,
+				   facebook_service_upload_photos,
+				   upload_photo_ready_cb,
+				   self);
 
 	g_free (uri);
 	soup_multipart_free (multipart);
@@ -707,7 +911,7 @@ facebook_service_upload_photos (FacebookService     *self,
 				GAsyncReadyCallback  callback,
 				gpointer             user_data)
 {
-	gth_task_progress (GTH_TASK (self->priv->conn),
+	gth_task_progress (GTH_TASK (self),
 			   _("Uploading the files to the server"),
 			   NULL,
 			   TRUE,
@@ -760,7 +964,7 @@ list_photos_ready_cb (SoupSession *session,
 	DomDocument        *doc = NULL;
 	GError             *error = NULL;
 
-	result = facebook_connection_get_result (self->priv->conn);
+	result = _web_service_get_result (WEB_SERVICE (self));
 
 	if (msg->status_code != 200) {
 		g_simple_async_result_set_error (result,
@@ -847,14 +1051,14 @@ facebook_service_list_photos (FacebookService       *self,
 	}
 	facebook_connection_add_api_sig (self->priv->conn, data_set);
 	msg = soup_form_request_new_from_hash ("GET", "http://api.facebook.com/services/rest";, data_set);
-	facebook_connection_send_message (self->priv->conn,
-					msg,
-					cancellable,
-					callback,
-					user_data,
-					facebook_service_list_photos,
-					list_photos_ready_cb,
-					self);
+	_web_service_send_message (self,
+				   msg,
+				   cancellable,
+				   callback,
+				   user_data,
+				   facebook_service_list_photos,
+				   list_photos_ready_cb,
+				   self);
 
 	g_hash_table_destroy (data_set);
 }
diff --git a/extensions/facebook/facebook-service.h b/extensions/facebook/facebook-service.h
index 8f065d2..800bf84 100644
--- a/extensions/facebook/facebook-service.h
+++ b/extensions/facebook/facebook-service.h
@@ -22,11 +22,11 @@
 #ifndef FACEBOOK_SERVICE_H
 #define FACEBOOK_SERVICE_H
 
-#include <glib-object.h>
-#include "facebook-connection.h"
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include <extensions/oauth/oauth.h>
 #include "facebook-album.h"
 #include "facebook-types.h"
-#include "facebook-user.h"
 
 #define FACEBOOK_TYPE_SERVICE         (facebook_service_get_type ())
 #define FACEBOOK_SERVICE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), FACEBOOK_TYPE_SERVICE, FacebookService))
@@ -41,24 +41,19 @@ typedef struct _FacebookServiceClass    FacebookServiceClass;
 
 struct _FacebookService
 {
-	GObject __parent;
+	WebService __parent;
 	FacebookServicePrivate *priv;
 };
 
 struct _FacebookServiceClass
 {
-	GObjectClass __parent_class;
+	WebServiceClass __parent_class;
 };
 
 GType             facebook_service_get_type                   (void) G_GNUC_CONST;
-FacebookService * facebook_service_new                        (FacebookConnection   *conn);
-void              facebook_service_get_user                   (FacebookService      *self,
-							       GCancellable         *cancellable,
-							       GAsyncReadyCallback   callback,
-							       gpointer              user_data);
-FacebookUser *    facebook_service_get_user_finish            (FacebookService      *self,
-						               GAsyncResult         *result,
-						               GError              **error);
+FacebookService * facebook_service_new                        (GCancellable         *cancellable,
+							       GtkWidget            *browser,
+							       GtkWidget            *dialog);
 void              facebook_service_get_albums                 (FacebookService      *self,
 							       GCancellable         *cancellable,
 							       GAsyncReadyCallback   callback,
diff --git a/extensions/oauth/Makefile.am b/extensions/oauth/Makefile.am
index f22c444..309fe22 100644
--- a/extensions/oauth/Makefile.am
+++ b/extensions/oauth/Makefile.am
@@ -19,7 +19,9 @@ liboauth_la_SOURCES = 				\
 	oauth-authentication.c			\
 	oauth-authentication.h			\
 	oauth-connection.c			\
-	oauth-connection.h
+	oauth-connection.h			\
+	web-service.c				\
+	web-service.h
 
 liboauth_la_CFLAGS = $(GTHUMB_CFLAGS) $(LIBSOUP_CFLAGS) $(LIBSECRET_CFLAGS) $(WEBKIT2_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
 liboauth_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
diff --git a/extensions/oauth/oauth-account.c b/extensions/oauth/oauth-account.c
index 39b3a5a..39ee4e5 100644
--- a/extensions/oauth/oauth-account.c
+++ b/extensions/oauth/oauth-account.c
@@ -215,7 +215,7 @@ oauth_account_create_element (DomDomizable *base,
 	if (self->name != NULL)
 		dom_element_set_attribute (element, "name", self->name);
 
-	/* Don't save the token in the configuration file if the keyring is
+	/* Do not save the token in the configuration file if the keyring is
 	 * available. */
 
 #ifdef HAVE_LIBSECRET
@@ -224,8 +224,8 @@ oauth_account_create_element (DomDomizable *base,
 	set_token = TRUE;
 #endif
 
-	if (set_token && (self->token != NULL))
-		dom_element_set_attribute (element, "token", self->token);
+	if (set_token && (self->token_secret != NULL))
+		dom_element_set_attribute (element, "token-secret", self->token_secret);
 
 	if (self->is_default)
 		dom_element_set_attribute (element, "default", "1");
@@ -246,7 +246,7 @@ oauth_account_load_from_element (DomDomizable *base,
 		      "id", dom_element_get_attribute (element, "id"),
 		      "username", dom_element_get_attribute (element, "username"),
 		      "name", dom_element_get_attribute (element, "name"),
-		      "token", dom_element_get_attribute (element, "token"),
+		      "token-secret", dom_element_get_attribute (element, "token-secret"),
 		      "is-default", (g_strcmp0 (dom_element_get_attribute (element, "default"), "1") == 0),
 		      NULL);
 }
diff --git a/extensions/oauth/oauth.h b/extensions/oauth/oauth.h
index 7db1151..6b0f5a3 100644
--- a/extensions/oauth/oauth.h
+++ b/extensions/oauth/oauth.h
@@ -28,5 +28,6 @@
 #include <extensions/oauth/oauth-account-manager-dialog.h>
 #include <extensions/oauth/oauth-authentication.h>
 #include <extensions/oauth/oauth-connection.h>
+#include <extensions/oauth/web-service.h>
 
 #endif /* OAUTH_H */
diff --git a/extensions/oauth/oauth2-ask-authorization-dialog.c b/extensions/oauth/oauth2-ask-authorization-dialog.c
index e3fef64..21cb092 100644
--- a/extensions/oauth/oauth2-ask-authorization-dialog.c
+++ b/extensions/oauth/oauth2-ask-authorization-dialog.c
@@ -108,13 +108,12 @@ oauth2_ask_authorization_dialog_init (OAuth2AskAuthorizationDialog *self)
 
 
 GtkWidget *
-oauth2_ask_authorization_dialog_new (const char *title,
-				     const char *uri)
+oauth2_ask_authorization_dialog_new (const char *uri)
 {
 	OAuth2AskAuthorizationDialog *self;
 
 	self = g_object_new (OAUTH2_TYPE_ASK_AUTHORIZATION_DIALOG,
-			     "title", title,
+			     "title", _("Authorization Required"),
 			     NULL);
 	webkit_web_view_load_uri (WEBKIT_WEB_VIEW (self->priv->view), uri);
 
diff --git a/extensions/oauth/oauth2-ask-authorization-dialog.h b/extensions/oauth/oauth2-ask-authorization-dialog.h
index 5c19239..31067ba 100644
--- a/extensions/oauth/oauth2-ask-authorization-dialog.h
+++ b/extensions/oauth/oauth2-ask-authorization-dialog.h
@@ -52,8 +52,7 @@ struct _OAuth2AskAuthorizationDialogClass {
 };
 
 GType          oauth2_ask_authorization_dialog_get_type     (void);
-GtkWidget *    oauth2_ask_authorization_dialog_new          (const char *title,
-							     const char *url);
+GtkWidget *    oauth2_ask_authorization_dialog_new          (const char                   *url);
 GtkWidget *    oauth2_ask_authorization_dialog_get_view     (OAuth2AskAuthorizationDialog *self);
 const char *   oauth2_ask_authorization_dialog_get_uri      (OAuth2AskAuthorizationDialog *self);
 
diff --git a/extensions/oauth/web-service.c b/extensions/oauth/web-service.c
new file mode 100644
index 0000000..b7c8b8f
--- /dev/null
+++ b/extensions/oauth/web-service.c
@@ -0,0 +1,852 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2012 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#ifdef HAVE_LIBSOUP_GNOME
+#include <libsoup/soup-gnome.h>
+#else
+#include <libsoup/soup.h>
+#endif /* HAVE_LIBSOUP_GNOME */
+#ifdef HAVE_LIBSECRET
+#include <libsecret/secret.h>
+#endif /* HAVE_LIBSECRET */
+#include "web-service.h"
+
+
+#undef  DEBUG_WEB_CONNECTION
+#define WEB_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT 2
+
+
+GQuark
+web_service_error_quark (void)
+{
+	static GQuark quark;
+
+        if (! quark)
+                quark = g_quark_from_static_string ("web-service-error");
+
+        return quark;
+}
+
+
+G_DEFINE_TYPE (WebService, web_service, GTH_TYPE_TASK)
+
+
+enum {
+        PROP_0,
+        PROP_SERVICE_NAME,
+        PROP_SERVICE_ADDRESS,
+        PROP_SERVICE_PROTOCOL,
+	PROP_CANCELLABLE,
+	PROP_BROWSER,
+	PROP_DIALOG
+};
+
+
+/* Signals */
+enum {
+	ACCOUNT_READY,
+	ACCOUNTS_CHANGED,
+	LAST_SIGNAL
+};
+
+
+static guint web_service_signals[LAST_SIGNAL] = { 0 };
+
+
+struct _WebServicePrivate
+{
+	char               *service_name;
+	char               *service_address;
+	char               *service_protocol;
+	SoupSession        *session;
+	SoupMessage        *msg;
+	GCancellable       *cancellable;
+	GSimpleAsyncResult *result;
+	GList              *accounts;
+	OAuthAccount       *account;
+	GtkWidget          *browser;
+	GtkWidget          *dialog;
+};
+
+
+static void
+web_service_finalize (GObject *object)
+{
+	WebService *self = WEB_SERVICE (object);
+
+	_g_object_unref (self->priv->account);
+	_g_object_list_unref (self->priv->accounts);
+	_g_object_unref (self->priv->result);
+	_g_object_unref (self->priv->cancellable);
+	_g_object_unref (self->priv->session);
+	g_free (self->priv->service_protocol);
+	g_free (self->priv->service_address);
+	g_free (self->priv->service_name);
+
+	G_OBJECT_CLASS (web_service_parent_class)->finalize (object);
+}
+
+
+static void
+web_service_set_property (GObject      *object,
+			  guint         property_id,
+			  const GValue *value,
+			  GParamSpec   *pspec)
+{
+	WebService *self = WEB_SERVICE (object);
+
+	switch (property_id) {
+	case PROP_SERVICE_NAME:
+		_g_strset (&self->priv->service_name, g_value_get_string (value));
+		break;
+	case PROP_SERVICE_ADDRESS:
+		_g_strset (&self->priv->service_address, g_value_get_string (value));
+		break;
+	case PROP_SERVICE_PROTOCOL:
+		_g_strset (&self->priv->service_protocol, g_value_get_string (value));
+		break;
+	case PROP_CANCELLABLE:
+		_g_object_unref (self->priv->cancellable);
+		self->priv->cancellable = g_value_dup_object (value);
+		break;
+	case PROP_BROWSER:
+		self->priv->browser = g_value_get_pointer (value);
+		break;
+	case PROP_DIALOG:
+		self->priv->dialog = g_value_get_pointer (value);
+		break;
+	default:
+		break;
+	}
+}
+
+
+static void
+web_service_get_property (GObject    *object,
+			  guint       property_id,
+			  GValue     *value,
+			  GParamSpec *pspec)
+{
+	WebService *self = WEB_SERVICE (object);
+
+	switch (property_id) {
+	case PROP_SERVICE_NAME:
+		g_value_set_string (value, self->priv->service_name);
+		break;
+	case PROP_SERVICE_ADDRESS:
+		g_value_set_string (value, self->priv->service_address);
+		break;
+	case PROP_SERVICE_PROTOCOL:
+		g_value_set_string (value, self->priv->service_protocol);
+		break;
+	case PROP_CANCELLABLE:
+		g_value_set_object (value, self->priv->cancellable);
+		break;
+	case PROP_BROWSER:
+		g_value_set_pointer (value, self->priv->browser);
+		break;
+	case PROP_DIALOG:
+		g_value_set_pointer (value, self->priv->dialog);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+
+static void
+web_service_constructed (GObject *object)
+{
+	WebService *self = WEB_SERVICE (object);
+
+	self->priv->accounts = oauth_accounts_load_from_file (self->priv->service_name, 0);
+	self->priv->account = oauth_accounts_find_default (self->priv->accounts);
+
+	if (G_OBJECT_CLASS (web_service_parent_class)->constructed != NULL)
+		G_OBJECT_CLASS (web_service_parent_class)->constructed (object);
+}
+
+
+static void
+web_service_exec (GthTask *base)
+{
+	/* void */
+}
+
+
+static void
+web_service_cancelled (GthTask *base)
+{
+	WebService *self = WEB_SERVICE (base);
+
+	if ((self->priv->session == NULL) || (self->priv->msg == NULL))
+		return;
+
+	soup_session_cancel_message (self->priv->session,
+				     self->priv->msg,
+				     SOUP_STATUS_CANCELLED);
+}
+
+
+static void
+web_service_class_init (WebServiceClass *klass)
+{
+	GObjectClass *object_class;
+	GthTaskClass *task_class;
+
+	g_type_class_add_private (klass, sizeof (WebServicePrivate));
+
+	object_class = (GObjectClass*) klass;
+	object_class->finalize = web_service_finalize;
+	object_class->set_property = web_service_set_property;
+	object_class->get_property = web_service_get_property;
+	object_class->constructed = web_service_constructed;
+
+	task_class = (GthTaskClass*) klass;
+	task_class->exec = web_service_exec;
+	task_class->cancelled = web_service_cancelled;
+
+	/* properties */
+
+	g_object_class_install_property (object_class,
+					 PROP_SERVICE_NAME,
+					 g_param_spec_string ("service-name",
+                                                              "Service Name",
+                                                              "",
+                                                              NULL,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+	g_object_class_install_property (object_class,
+					 PROP_SERVICE_ADDRESS,
+					 g_param_spec_string ("service-address",
+                                                              "Service Address",
+                                                              "",
+                                                              NULL,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+	g_object_class_install_property (object_class,
+					 PROP_SERVICE_PROTOCOL,
+					 g_param_spec_string ("service-protocol",
+                                                              "Service Protocol",
+                                                              "",
+                                                              NULL,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+	g_object_class_install_property (object_class,
+					 PROP_CANCELLABLE,
+					 g_param_spec_object ("cancellable",
+                                                              "Cancellable",
+                                                              "",
+                                                              G_TYPE_CANCELLABLE,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_BROWSER,
+					 g_param_spec_pointer ("browser",
+                                                               "Browser",
+                                                               "",
+                                                               G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_DIALOG,
+					 g_param_spec_pointer ("dialog",
+                                                               "Dialog",
+                                                               "",
+                                                               G_PARAM_READWRITE));
+
+	/* signals */
+
+	web_service_signals[ACCOUNT_READY] =
+		g_signal_new ("account-ready",
+			      G_TYPE_FROM_CLASS (klass),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (WebServiceClass, account_ready),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+	web_service_signals[ACCOUNTS_CHANGED] =
+		g_signal_new ("accounts-changed",
+			      G_TYPE_FROM_CLASS (klass),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (WebServiceClass, accounts_changed),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+}
+
+
+static void
+web_service_init (WebService *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, WEB_TYPE_SERVICE, WebServicePrivate);
+	self->priv->session = NULL;
+	self->priv->msg = NULL;
+	self->priv->cancellable = NULL;
+	self->priv->result = NULL;
+	self->priv->accounts = NULL;
+	self->priv->account = NULL;
+	self->priv->cancellable = NULL;
+	self->priv->browser = NULL;
+	self->priv->dialog = NULL;
+}
+
+
+/* -- authentication error dialog -- */
+
+
+static void show_choose_account_dialog (WebService *self);
+
+
+static void
+authentication_error_dialog_response_cb (GtkDialog *dialog,
+					 int        response_id,
+					 gpointer   user_data)
+{
+	WebService *self = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gtk_dialog_response (GTK_DIALOG (self->priv->dialog), GTK_RESPONSE_DELETE_EVENT);
+		break;
+
+	case WEB_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		show_choose_account_dialog (self);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+show_authentication_error_dialog (WebService  *self,
+				  GError     **error)
+{
+	GtkWidget *dialog;
+
+	if (g_error_matches (*error, WEB_SERVICE_ERROR, WEB_SERVICE_ERROR_TOKEN_EXPIRED)) {
+		web_service_ask_authorization (self);
+		return;
+	}
+
+	dialog = _gtk_message_dialog_new (GTK_WINDOW (self->priv->browser),
+					  GTK_DIALOG_MODAL,
+					  GTK_STOCK_DIALOG_ERROR,
+					  _("Could not connect to the server"),
+					  (*error)->message,
+					  _("Choose _Account..."), WEB_AUTHENTICATION_RESPONSE_CHOOSE_ACCOUNT,
+					  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					  NULL);
+	gth_task_dialog (GTH_TASK (self), TRUE, dialog);
+
+	g_signal_connect (dialog,
+			  "delete-event",
+			  G_CALLBACK (gtk_true),
+			  NULL);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (authentication_error_dialog_response_cb),
+			  self);
+	gtk_widget_show (dialog);
+
+	g_clear_error (error);
+}
+
+
+/* -- web_service_autoconnect -- */
+
+
+static void
+set_current_account (WebService *self,
+		     OAuthAccount    *account)
+{
+	GList *link;
+
+	link = g_list_find_custom (self->priv->accounts, self->priv->account, (GCompareFunc) oauth_account_cmp);
+	if (link != NULL) {
+		self->priv->accounts = g_list_remove_link (self->priv->accounts, link);
+		_g_object_list_unref (link);
+	}
+
+	_g_object_unref (self->priv->account);
+	self->priv->account = NULL;
+
+	if (account != NULL) {
+		self->priv->account = g_object_ref (account);
+		self->priv->accounts = g_list_prepend (self->priv->accounts, g_object_ref (self->priv->account));
+	}
+}
+
+
+#ifdef HAVE_LIBSECRET
+static void
+password_store_ready_cb (GObject      *source_object,
+			 GAsyncResult *result,
+			 gpointer      user_data)
+{
+	WebService *self = user_data;
+
+	secret_password_store_finish (result, NULL);
+	web_service_account_ready (self);
+}
+#endif
+
+
+static void
+get_user_info_ready_cb (GObject      *source_object,
+			GAsyncResult *res,
+			gpointer      user_data)
+{
+	WebService   *self = user_data;
+	GError       *error = NULL;
+	OAuthAccount *account;
+
+	account = web_service_get_user_info_finish (self, res, &error);
+	if (account == NULL) {
+		show_authentication_error_dialog (self, &error);
+		return;
+	}
+
+	set_current_account (self, account);
+	oauth_accounts_save_to_file (self->priv->service_name,
+				     self->priv->accounts,
+				     self->priv->account);
+
+#ifdef HAVE_LIBSECRET
+	{
+		secret_password_store (SECRET_SCHEMA_COMPAT_NETWORK,
+				       NULL,
+				       "Web",
+				       account->token_secret,
+				       self->priv->cancellable,
+				       password_store_ready_cb,
+				       self,
+				       "user", account->id,
+				       "server", self->priv->service_address,
+				       "protocol", self->priv->service_protocol,
+				       NULL);
+	}
+#else
+	web_service_account_ready (self);
+#endif
+
+	g_object_unref (account);
+}
+
+
+static void
+connect_to_server_step2 (WebService *self)
+{
+	if (self->priv->account->token_secret == NULL) {
+		web_service_ask_authorization (self);
+		return;
+	}
+	web_service_get_user_info (self,
+				   self->priv->cancellable,
+				   get_user_info_ready_cb,
+				   self);
+}
+
+
+#ifdef HAVE_LIBSECRET
+static void
+password_lookup_ready_cb (GObject      *source_object,
+			  GAsyncResult *result,
+			  gpointer      user_data)
+{
+	WebService *self = user_data;
+	char       *secret;
+
+	secret = secret_password_lookup_finish (result, NULL);
+	if (secret != NULL) {
+		g_object_set (G_OBJECT (self->priv->account), "token-secret", secret, NULL);
+		g_free (secret);
+	}
+
+	connect_to_server_step2 (self);
+}
+#endif
+
+
+static void
+connect_to_server (WebService *self)
+{
+	g_return_if_fail (self->priv->account != NULL);
+	g_return_if_fail (self->priv->account->id != NULL);
+
+#ifdef HAVE_LIBSECRET
+	if (self->priv->account->token_secret == NULL) {
+		secret_password_lookup (SECRET_SCHEMA_COMPAT_NETWORK,
+					self->priv->cancellable,
+					password_lookup_ready_cb,
+					self,
+					"user", self->priv->account->id,
+					"server", self->priv->service_address,
+					"protocol", self->priv->service_protocol,
+					NULL);
+		return;
+	}
+#endif
+
+	connect_to_server_step2 (self);
+}
+
+
+static void
+account_chooser_dialog_response_cb (GtkDialog *dialog,
+				    int        response_id,
+				    gpointer   user_data)
+{
+	WebService *self = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gtk_dialog_response (GTK_DIALOG (self->priv->dialog), GTK_RESPONSE_DELETE_EVENT);
+		break;
+
+	case GTK_RESPONSE_OK:
+		_g_object_unref (self->priv->account);
+		self->priv->account = oauth_account_chooser_dialog_get_active (OAUTH_ACCOUNT_CHOOSER_DIALOG (dialog));
+		if (self->priv->account != NULL) {
+			gtk_widget_destroy (GTK_WIDGET (dialog));
+			connect_to_server (self);
+		}
+		break;
+
+	case OAUTH_ACCOUNT_CHOOSER_RESPONSE_NEW:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		web_service_ask_authorization (self);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+show_choose_account_dialog (WebService *self)
+{
+	GtkWidget *dialog;
+
+	gth_task_dialog (GTH_TASK (self), TRUE, NULL);
+	dialog = oauth_account_chooser_dialog_new (self->priv->accounts, self->priv->account);
+	g_signal_connect (dialog,
+			  "delete-event",
+			  G_CALLBACK (gtk_true),
+			  NULL);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (account_chooser_dialog_response_cb),
+			  self);
+
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Choose Account"));
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->browser));
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_present (GTK_WINDOW (dialog));
+}
+
+
+void
+web_service_autoconnect (WebService *self)
+{
+	gtk_widget_hide (self->priv->dialog);
+	gth_task_dialog (GTH_TASK (self), FALSE, NULL);
+
+	if (self->priv->accounts != NULL) {
+		if (self->priv->account != NULL) {
+			connect_to_server (self);
+		}
+		else if (self->priv->accounts->next == NULL) {
+			self->priv->account = g_object_ref (self->priv->accounts->data);
+			connect_to_server (self);
+		}
+		else
+			show_choose_account_dialog (self);
+	}
+	else
+		web_service_ask_authorization (self);
+}
+
+
+void
+web_service_connect (WebService   *self,
+		     OAuthAccount *account)
+{
+	set_current_account (self, account);
+	web_service_autoconnect (self);
+}
+
+
+void
+web_service_set_current_account (WebService   *self,
+				 OAuthAccount *account)
+{
+	if (account == self->priv->account)
+		return;
+	_g_object_unref (self->priv->account);
+	self->priv->account = _g_object_ref (account);
+}
+
+
+OAuthAccount *
+web_service_get_current_account (WebService *self)
+{
+	return self->priv->account;
+}
+
+
+GList *
+web_service_get_accounts (WebService *self)
+{
+	return self->priv->accounts;
+}
+
+
+/* -- web_service_edit_accounts -- */
+
+
+static void
+account_manager_dialog_response_cb (GtkDialog *dialog,
+			            int        response_id,
+			            gpointer   user_data)
+{
+	WebService *self = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+
+	case GTK_RESPONSE_OK:
+		_g_object_list_unref (self->priv->accounts);
+		self->priv->accounts = oauth_account_manager_dialog_get_accounts (OAUTH_ACCOUNT_MANAGER_DIALOG (dialog));
+		if (! g_list_find_custom (self->priv->accounts, self->priv->account, (GCompareFunc) oauth_account_cmp)) {
+			_g_object_unref (self->priv->account);
+			self->priv->account = NULL;
+			web_service_autoconnect (self);
+		}
+		else
+			g_signal_emit (self, web_service_signals[ACCOUNTS_CHANGED], 0);
+		oauth_accounts_save_to_file (self->priv->service_name, self->priv->accounts, self->priv->account);
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		break;
+
+	case OAUTH_ACCOUNT_CHOOSER_RESPONSE_NEW:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		web_service_ask_authorization (self);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+void
+web_service_edit_accounts (WebService *self,
+			   GtkWindow  *parent)
+{
+	GtkWidget *dialog;
+
+	dialog = oauth_account_manager_dialog_new (self->priv->accounts);
+	g_signal_connect (dialog,
+			  "delete-event",
+			  G_CALLBACK (gtk_true),
+			  NULL);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (account_manager_dialog_response_cb),
+			  self);
+
+	gtk_window_set_title (GTK_WINDOW (dialog), _("Edit Accounts"));
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->dialog));
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	gtk_window_present (GTK_WINDOW (dialog));
+}
+
+
+void
+web_service_account_ready (WebService *self)
+{
+	g_signal_emit (self, web_service_signals[ACCOUNT_READY], 0);
+}
+
+
+void
+web_service_ask_authorization (WebService *self)
+{
+	WEB_SERVICE_GET_CLASS (self)->ask_authorization (self);
+}
+
+
+void
+web_service_get_user_info (WebService		 *self,
+			   GCancellable		 *cancellable,
+			   GAsyncReadyCallback	  callback,
+			   gpointer		  user_data)
+{
+	gth_task_progress (GTH_TASK (self),
+			   _("Connecting to the server"),
+			   _("Getting account information"),
+			   TRUE,
+			   0.0);
+
+	WEB_SERVICE_GET_CLASS (self)->get_user_info (self, cancellable, callback, user_data);
+}
+
+
+OAuthAccount *
+web_service_get_user_info_finish (WebService	   *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)));
+}
+
+
+/* -- connection utilities -- */
+
+
+void
+_web_service_send_message (WebService          *self,
+			   SoupMessage         *msg,
+			   GCancellable        *cancellable,
+			   GAsyncReadyCallback  callback,
+			   gpointer             user_data,
+			   gpointer             source_tag,
+			   SoupSessionCallback  soup_session_cb,
+			   gpointer             soup_session_cb_data)
+{
+	if (self->priv->session == NULL) {
+		self->priv->session = soup_session_async_new_with_options (
+#ifdef HAVE_LIBSOUP_GNOME
+			SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_GNOME,
+#endif
+			NULL);
+
+#ifdef DEBUG_WEB_CONNECTION
+		{
+			SoupLogger *logger;
+
+			logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
+			soup_session_add_feature (self->priv->session, SOUP_SESSION_FEATURE (logger));
+
+			g_object_unref (logger);
+		}
+#endif
+	}
+
+	_g_object_unref (self->priv->cancellable);
+	self->priv->cancellable = _g_object_ref (cancellable);
+
+	_g_object_unref (self->priv->result);
+	self->priv->result = g_simple_async_result_new (G_OBJECT (self),
+							callback,
+							user_data,
+							source_tag);
+
+	self->priv->msg = msg;
+	g_object_add_weak_pointer (G_OBJECT (msg), (gpointer *) &self->priv->msg);
+
+	soup_session_queue_message (self->priv->session,
+				    msg,
+				    soup_session_cb,
+				    soup_session_cb_data);
+}
+
+
+GSimpleAsyncResult *
+_web_service_get_result (WebService *self)
+{
+	return self->priv->result;
+}
+
+
+SoupMessage *
+_web_service_get_message (WebService *self)
+{
+	return self->priv->msg;
+}
+
+
+/* -- _web_service_set_auth_dialog -- */
+
+
+static void
+ask_authorization_dialog_response_cb (GtkDialog *dialog,
+				      int        response_id,
+				      gpointer   user_data)
+{
+	WebService *self = user_data;
+
+	switch (response_id) {
+	case GTK_RESPONSE_DELETE_EVENT:
+	case GTK_RESPONSE_CANCEL:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gtk_dialog_response (GTK_DIALOG (self->priv->dialog), GTK_RESPONSE_DELETE_EVENT);
+		break;
+
+	case GTK_RESPONSE_OK:
+		gtk_widget_destroy (GTK_WIDGET (dialog));
+		gth_task_dialog (GTH_TASK (self), FALSE, NULL);
+		web_service_get_user_info (self,
+					   self->priv->cancellable,
+					   get_user_info_ready_cb,
+					   self);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+void
+_web_service_set_auth_dialog (WebService *self,
+			      GtkDialog  *dialog)
+{
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+	if (gtk_widget_get_visible (self->priv->dialog))
+		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->dialog));
+	else
+		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (self->priv->browser));
+
+	g_signal_connect (dialog,
+			  "delete-event",
+			  G_CALLBACK (gtk_true),
+			  NULL);
+	g_signal_connect (dialog,
+			  "response",
+			  G_CALLBACK (ask_authorization_dialog_response_cb),
+			  self);
+}
diff --git a/extensions/oauth/web-service.h b/extensions/oauth/web-service.h
new file mode 100644
index 0000000..8526555
--- /dev/null
+++ b/extensions/oauth/web-service.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2012 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WEB_SERVICE_H
+#define WEB_SERVICE_H
+
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "oauth.h"
+
+#define WEB_SERVICE_ERROR web_service_error_quark()
+#define WEB_SERVICE_ERROR_TOKEN_EXPIRED 1
+
+GQuark web_service_error_quark (void);
+
+typedef enum {
+	WEB_AUTHORIZATION_READ,
+	WEB_AUTHORIZATION_WRITE
+} WebAuthorizationType;
+
+#define WEB_TYPE_SERVICE         (web_service_get_type ())
+#define WEB_SERVICE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), WEB_TYPE_SERVICE, WebService))
+#define WEB_SERVICE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), WEB_TYPE_SERVICE, WebServiceClass))
+#define WEB_IS_SERVICE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), WEB_TYPE_SERVICE))
+#define WEB_IS_SERVICE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), WEB_TYPE_SERVICE))
+#define WEB_SERVICE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS((o), WEB_TYPE_SERVICE, WebServiceClass))
+
+typedef struct _WebService         WebService;
+typedef struct _WebServicePrivate  WebServicePrivate;
+typedef struct _WebServiceClass    WebServiceClass;
+
+struct _WebService
+{
+	GthTask __parent;
+	WebServicePrivate *priv;
+};
+
+struct _WebServiceClass
+{
+	GthTaskClass __parent_class;
+
+	/*< signals >*/
+
+	void  (*account_ready)		(WebService		 *self);
+	void  (*accounts_changed)	(WebService		 *self);
+
+	/*< virtual functions >*/
+
+	void  (*ask_authorization)	(WebService		 *self);
+	void  (*get_user_info)		(WebService		 *self,
+					 GCancellable		 *cancellable,
+					 GAsyncReadyCallback	  callback,
+					 gpointer		  user_data);
+};
+
+GType           web_service_get_type		(void) G_GNUC_CONST;
+void            web_service_autoconnect		(WebService		 *self);
+void            web_service_connect		(WebService		 *self,
+						 OAuthAccount		 *account);
+void		web_service_set_current_account	(WebService		 *self,
+						 OAuthAccount            *account);
+OAuthAccount *  web_service_get_current_account	(WebService		 *self);
+GList *         web_service_get_accounts	(WebService		 *self);
+void            web_service_edit_accounts	(WebService		 *self,
+						 GtkWindow		 *parent);
+void            web_service_account_ready	(WebService		 *self);
+void            web_service_ask_authorization	(WebService		 *self);
+void            web_service_get_user_info       (WebService		 *self,
+						 GCancellable		 *cancellable,
+						 GAsyncReadyCallback	  callback,
+						 gpointer		  user_data);
+OAuthAccount *  web_service_get_user_info_finish(WebService		 *self,
+						 GAsyncResult		 *result,
+						 GError			**error);
+
+/* -- utilities -- */
+
+void            _web_service_send_message	(WebService		 *self,
+						 SoupMessage		 *msg,
+						 GCancellable		 *cancellable,
+						 GAsyncReadyCallback	  callback,
+						 gpointer		  user_data,
+						 gpointer		  source_tag,
+						 SoupSessionCallback	  soup_session_cb,
+						 gpointer		  soup_session_cb_data);
+GSimpleAsyncResult *
+		_web_service_get_result		(WebService		 *self);
+SoupMessage *	_web_service_get_message	(WebService		 *self);
+void            _web_service_set_auth_dialog	(WebService		 *self,
+						 GtkDialog               *dialog);
+
+#endif /* WEB_SERVICE_H */
+



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