[gnome-online-accounts] Add Last.fm
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-online-accounts] Add Last.fm
- Date: Mon, 14 Sep 2015 17:11:23 +0000 (UTC)
commit 76a30bd24ff74ab40581adb3868efdc8c8f4bffe
Author: Felipe Borges <felipe10borges gmail com>
Date: Fri Jul 3 21:50:20 2015 -0300
Add Last.fm
https://bugzilla.gnome.org/show_bug.cgi?id=728621
configure.ac | 33 ++
doc/goa-docs.xml | 1 +
doc/goa-sections.txt | 9 +
doc/goa.types | 1 +
gnome-online-accounts.doap | 4 +-
po/POTFILES.in | 1 +
src/examples/Makefile.am | 6 +-
src/examples/lastfm-shout.c | 109 +++++
src/goabackend/Makefile.am | 1 +
src/goabackend/goalastfmprovider.c | 920 ++++++++++++++++++++++++++++++++++++
src/goabackend/goalastfmprovider.h | 40 ++
src/goabackend/goaprovider.c | 4 +
12 files changed, 1126 insertions(+), 3 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 389636a..c0007a7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -394,6 +394,38 @@ if test "$enable_kerberos" != "no"; then
fi
AM_CONDITIONAL(BUILD_KERBEROS, [test x$enable_kerberos != xno])
+# Last.fm
+AC_DEFINE(GOA_LASTFM_NAME, ["lastfm"], [ProviderType and extension point name])
+AC_ARG_ENABLE([lastfm], [AS_HELP_STRING([--enable-lastfm],
+ [Enable Last.fm provider])],
+ [],
+ [enable_lastfm=no])
+AC_ARG_WITH(lastfm-client-id,
+ [AS_HELP_STRING([--with-lastfm-client-id],
+ [Last.fm client id])],
+ [],
+ [])
+AC_ARG_WITH(lastfm-client-secret,
+ [AS_HELP_STRING([--with-lastfm-client-secret],
+ [Last.fm client secret])],
+ [],
+ [])
+if test "$with_lastfm_client_id" = ""; then
+ with_lastfm_client_id="7a2461fe34c9c8124fb28ac750ba12fa"
+fi
+if test "$with_lastfm_client_secret" = ""; then
+ with_lastfm_client_secret="49ec391644459c417f3afe57ca246c5a"
+fi
+AC_DEFINE_UNQUOTED(GOA_LASTFM_CLIENT_ID,
+ ["$with_lastfm_client_id"],
+ [LastFM client id])
+AC_DEFINE_UNQUOTED(GOA_LASTFM_CLIENT_SECRET,
+ ["$with_lastfm_client_secret"],
+ [LastFM client secret])
+if test "$enable_lastfm" != "no"; then
+ AC_DEFINE(GOA_LASTFM_ENABLED, 1, [Enable LastFM data provider])
+fi
+
# Optional timerfd support
AC_MSG_CHECKING([for timerfd support])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
@@ -556,6 +588,7 @@ echo "
Windows Live provider: ${enable_windows_live} (OAuth 2.0, id:${with_windows_live_client_id})
Telepathy provider: ${enable_telepathy}
Pocket provider: ${enable_pocket} (id:${with_pocket_client_id})
+ Last.fm provider: ${enable_lastfm} (id:${with_lastfm_client_id}
secret:${with_lastfm_client_secret})
Maintainer mode: ${USE_MAINTAINER_MODE}
Building api docs: ${enable_gtk_doc}
diff --git a/doc/goa-docs.xml b/doc/goa-docs.xml
index 55a3558..cdce050 100644
--- a/doc/goa-docs.xml
+++ b/doc/goa-docs.xml
@@ -181,6 +181,7 @@
<xi:include href="xml/goaflickrprovider.xml"/>
<xi:include href="xml/goafoursquareprovider.xml"/>
<xi:include href="xml/goatelepathyprovider.xml"/>
+ <xi:include href="xml/goalastfmprovider.xml"/>
</chapter>
</part>
diff --git a/doc/goa-sections.txt b/doc/goa-sections.txt
index 73509c4..152d13e 100644
--- a/doc/goa-sections.txt
+++ b/doc/goa-sections.txt
@@ -604,6 +604,15 @@ GOA_TYPE_TELEPATHY_PROVIDER
goa_telepathy_provider_get_type
</SECTION>
+<FILE>goalastfmprovider</FILE>
+GoaLastfmProvider
+<SUBSECTION Standard>
+GOA_LASTFM_PROVIDER
+GOA_IS_LASTFM_PROVIDER
+GOA_TYPE_LASTFM_PROVIDER
+goa_lastfm_provider_get_type
+</SECTION>
+
<SECTION>
<FILE>GoaMail</FILE>
GoaMail
diff --git a/doc/goa.types b/doc/goa.types
index c2daed1..98354b7 100644
--- a/doc/goa.types
+++ b/doc/goa.types
@@ -70,3 +70,4 @@ goa_media_server_provider_get_type
goa_flickr_provider_get_type
goa_foursquare_provider_get_type
goa_windows_live_provider_get_type
+goa_lastfm_provider_get_type
diff --git a/gnome-online-accounts.doap b/gnome-online-accounts.doap
index bd47309..9c82bbb 100644
--- a/gnome-online-accounts.doap
+++ b/gnome-online-accounts.doap
@@ -15,8 +15,8 @@
GNOME Online Accounts provides interfaces so that applications and
libraries in GNOME can access the user's online accounts. It has
providers for Google, ownCloud, Facebook, Flickr, Windows Live,
- Pocket, Foursquare, Microsoft Exchange, IMAP/SMTP, Jabber, SIP and
- Kerberos.
+ Pocket, Foursquare, Microsoft Exchange, Last.fm, IMAP/SMTP, Jabber,
+ SIP and Kerberos.
</description>
<homepage rdf:resource="https://wiki.gnome.org/Projects/GnomeOnlineAccounts" />
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0dde404..b8c066d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -13,6 +13,7 @@ src/goabackend/goahttpclient.c
src/goabackend/goaimapauthlogin.c
src/goabackend/goaimapsmtpprovider.c
src/goabackend/goakerberosprovider.c
+src/goabackend/goalastfmprovider.c
src/goabackend/goamediaserverprovider.c
src/goabackend/goaoauth2provider.c
src/goabackend/goaoauthprovider.c
diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am
index 3e9458c..e837d9a 100644
--- a/src/examples/Makefile.am
+++ b/src/examples/Makefile.am
@@ -15,12 +15,16 @@ AM_CPPFLAGS = \
$(WARN_CFLAGS) \
$(NULL)
-noinst_PROGRAMS = list-accounts
+noinst_PROGRAMS = list-accounts lastfm-shout
list_accounts_SOURCES = list-accounts.c
list_accounts_CFLAGS = $(GLIB_CFLAGS)
list_accounts_LDADD = $(GLIB_LIBS) ../goa/libgoa-1.0.la
+lastfm_shout_SOURCES = lastfm-shout.c
+lastfm_shout_CFLAGS = $(GLIB_CFLAGS) $(REST_CFLAGS)
+lastfm_shout_LDADD = $(GLIB_LIBS) $(REST_LIBS) ../goa/libgoa-1.0.la
+
if BUILD_BACKEND
noinst_PROGRAMS += list-providers add-pocket
diff --git a/src/examples/lastfm-shout.c b/src/examples/lastfm-shout.c
new file mode 100644
index 0000000..25a796c
--- /dev/null
+++ b/src/examples/lastfm-shout.c
@@ -0,0 +1,109 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2015 Felipe Borges
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#define GOA_API_IS_SUBJECT_TO_CHANGE
+#include <goa/goa.h>
+
+#include <rest/rest-proxy.h>
+#include <rest/rest-proxy-call.h>
+
+int
+main (int argc, char **argv)
+{
+ GError *error = NULL;
+ GoaClient *client;
+ GList *accounts, *l;
+ GoaAccount *account;
+ GoaOAuth2Based *oauth2 = NULL;
+ char *access_token = NULL;
+ RestProxy *proxy;
+ RestProxyCall *call;
+ gchar *sig, *api_signature;
+ const char *user;
+ const char *message = NULL;
+
+ if (argc != 2 && argc != 3) {
+ g_print ("Usage: %s USER [MESSAGE]\n", argv[0]);
+ return 1;
+ }
+ user = argv[1];
+ message = argv[2];
+
+ client = goa_client_new_sync (NULL, &error);
+ if (!client) {
+ g_error ("Could not create GoaClient: %s", error->message);
+ return 1;
+ }
+
+ accounts = goa_client_get_accounts (client);
+ for (l = accounts; l != NULL; l = l->next) {
+ account = goa_object_get_account (GOA_OBJECT (l->data));
+ if (g_strcmp0(goa_account_get_provider_name (account), "Last.fm") == 0) {
+ g_object_ref (account);
+ oauth2 = goa_object_get_oauth2_based (GOA_OBJECT (l->data));
+ break;
+ }
+ }
+
+ g_list_free_full (accounts, (GDestroyNotify) g_object_unref);
+
+ g_assert (account);
+ g_assert (oauth2);
+
+ if (!goa_oauth2_based_call_get_access_token_sync (oauth2, &access_token, NULL, NULL, &error)) {
+ g_error ("Could not get access token %s\n", error->message);
+ return 1;
+ }
+
+ g_print ("Got access token\n");
+
+ sig = g_strdup_printf ("api_key%s"
+ "message%s"
+ "methoduser.shout"
+ "sk%s"
+ "user%s%s",
+ goa_oauth2_based_get_client_id (oauth2),
+ message,
+ access_token,
+ user,
+ goa_oauth2_based_get_client_secret (oauth2));
+
+ api_signature = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig, -1);
+
+ proxy = rest_proxy_new ("https://ws.audioscrobbler.com/2.0/", FALSE);
+ call = rest_proxy_new_call (proxy);
+ rest_proxy_call_set_method (call, "POST");
+ rest_proxy_call_add_header (call, "Content-Type", "application/x-www-form-urlencoded");
+ rest_proxy_call_add_param (call, "api_key", goa_oauth2_based_get_client_id (oauth2));
+ rest_proxy_call_add_param (call, "method", "user.shout");
+ rest_proxy_call_add_param (call, "message", message);
+ rest_proxy_call_add_param (call, "user", user);
+ rest_proxy_call_add_param (call, "sk", access_token);
+ rest_proxy_call_add_param (call, "api_sig", api_signature);
+
+ if (!rest_proxy_call_sync (call, &error)) {
+ g_error ("Cannot shout message to user %s: %s", user, error->message);
+ return 1;
+ }
+
+ g_print ("Message sent!\n");
+
+ return 0;
+}
diff --git a/src/goabackend/Makefile.am b/src/goabackend/Makefile.am
index 06467ec..64a7e99 100644
--- a/src/goabackend/Makefile.am
+++ b/src/goabackend/Makefile.am
@@ -93,6 +93,7 @@ libgoa_backend_1_0_la_SOURCES = \
goatelepathyfactory.h goatelepathyfactory.c \
goatelepathyprovider.h goatelepathyprovider.c \
goapocketprovider.h goapocketprovider.c \
+ goalastfmprovider.h goalastfmprovider.c \
goautils.h goautils.c \
goawebview.h goawebview.c \
nautilus-floating-bar.h nautilus-floating-bar.c \
diff --git a/src/goabackend/goalastfmprovider.c b/src/goabackend/goalastfmprovider.c
new file mode 100644
index 0000000..ca728a6
--- /dev/null
+++ b/src/goabackend/goalastfmprovider.c
@@ -0,0 +1,920 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2015 Felipe Borges
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include <rest/rest-proxy.h>
+#include <json-glib/json-glib.h>
+
+#include "goahttpclient.h"
+#include "goaprovider.h"
+#include "goaprovider-priv.h"
+#include "goaoauth2provider.h"
+#include "goalastfmprovider.h"
+#include "goautils.h"
+
+struct _GoaLastfmProvider
+{
+ /*< private >*/
+ GoaOAuth2Provider parent_instance;
+};
+
+typedef struct _GoaLastfmProviderClass GoaLastfmProviderClass;
+
+struct _GoaLastfmProviderClass
+{
+ GoaOAuth2ProviderClass parent_class;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GoaLastfmProvider, goa_lastfm_provider, GOA_TYPE_OAUTH2_PROVIDER,
+ goa_provider_ensure_extension_points_registered ();
+ g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME,
+ g_define_type_id,
+ GOA_LASTFM_NAME,
+ 0));
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const gchar *
+get_provider_type (GoaProvider *_provider)
+{
+ return GOA_LASTFM_NAME;
+}
+
+static gchar *
+get_provider_name (GoaProvider *_provider,
+ GoaObject *object)
+{
+ return g_strdup (_("Last.fm"));
+}
+
+static GoaProviderGroup
+get_provider_group (GoaProvider *_provider)
+{
+ return GOA_PROVIDER_GROUP_BRANDED;
+}
+
+static GoaProviderFeatures
+get_provider_features (GoaProvider *_provider)
+{
+ return GOA_PROVIDER_FEATURE_BRANDED | GOA_PROVIDER_FEATURE_MUSIC;
+}
+
+static const gchar *
+get_request_uri (GoaProvider *provider)
+{
+ return "https://ws.audioscrobbler.com/2.0/";
+}
+
+static const gchar *
+get_client_id (GoaOAuth2Provider *provider)
+{
+ return GOA_LASTFM_CLIENT_ID;
+}
+
+static const gchar *
+get_client_secret (GoaOAuth2Provider *provider)
+{
+ return GOA_LASTFM_CLIENT_SECRET;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+build_object (GoaProvider *provider,
+ GoaObjectSkeleton *object,
+ GKeyFile *key_file,
+ const gchar *group,
+ GDBusConnection *connection,
+ gboolean just_added,
+ GError **error)
+{
+ GoaAccount *account;
+ GoaMusic *music = NULL;
+ gboolean music_enabled;
+ gboolean ret = FALSE;
+
+ account = NULL;
+
+ /* Chain up */
+ if (!GOA_PROVIDER_CLASS (goa_lastfm_provider_parent_class)->build_object (provider,
+ object,
+ key_file,
+ group,
+ connection,
+ just_added,
+ error))
+ goto out;
+
+ account = goa_object_get_account (GOA_OBJECT (object));
+
+ /* Music */
+ music = goa_object_get_music (GOA_OBJECT (object));
+ music_enabled = g_key_file_get_boolean (key_file, group, "MusicEnabled", NULL);
+ if (music_enabled)
+ {
+ if (music == NULL)
+ {
+ music = goa_music_skeleton_new ();
+ goa_object_skeleton_set_music (object, music);
+ }
+ }
+ else
+ {
+ if (music != NULL)
+ goa_object_skeleton_set_music (object, NULL);
+ }
+
+ if (just_added)
+ {
+ goa_account_set_music_disabled (account, !music_enabled);
+
+ g_signal_connect (account,
+ "notify::music-disabled",
+ G_CALLBACK (goa_util_account_notify_property_cb),
+ "MusicEnabled");
+ }
+
+ ret = TRUE;
+
+ out:
+ g_clear_object (&music);
+ g_clear_object (&account);
+ return ret;
+}
+
+static gboolean
+lastfm_login_sync (GoaProvider *provider,
+ const gchar *username,
+ const gchar *password,
+ GError **error)
+{
+ JsonParser *parser;
+ JsonObject *json_obj;
+ JsonObject *session_obj;
+ JsonNode *root;
+ RestProxyCall *call;
+ const gchar *payload;
+ gchar *sig;
+ gchar *sig_md5;
+ gboolean ret;
+
+ call = NULL;
+ parser = NULL;
+ ret = FALSE;
+
+ sig = g_strdup_printf ("api_key%s"
+ "methodauth.getMobileSession"
+ "password%s"
+ "username%s"
+ "%s",
+ GOA_LASTFM_CLIENT_ID,
+ password,
+ username,
+ GOA_LASTFM_CLIENT_SECRET);
+ sig_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig, -1);
+
+ call = rest_proxy_new_call (rest_proxy_new (get_request_uri (provider), FALSE));
+
+ rest_proxy_call_set_method (call, "POST");
+ rest_proxy_call_add_header (call, "Content-Type", "application/x-www-form-urlencoded");
+ rest_proxy_call_add_param (call, "method", "auth.getMobileSession");
+ rest_proxy_call_add_param (call, "api_key", GOA_LASTFM_CLIENT_ID);
+ rest_proxy_call_add_param (call, "username", username);
+ rest_proxy_call_add_param (call, "password", password);
+ rest_proxy_call_add_param (call, "api_sig", sig_md5);
+ rest_proxy_call_add_param (call, "format", "json");
+
+ if (!rest_proxy_call_sync (call, error))
+ goto out;
+
+ parser = json_parser_new ();
+ payload = rest_proxy_call_get_payload (call);
+ if (payload == NULL)
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
+ goto out;
+ }
+
+ if (!json_parser_load_from_data (parser,
+ payload,
+ rest_proxy_call_get_payload_length (call),
+ NULL))
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
+ goto out;
+ }
+
+ root = json_parser_get_root (parser);
+ json_obj = json_node_get_object (root);
+ session_obj = json_node_get_object (json_object_get_member (json_obj, "session"));
+
+ if (g_strdup (json_object_get_string_member (session_obj, "name")) == NULL)
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
+ goto out;
+ }
+
+
+ if (g_strdup (json_object_get_string_member (session_obj, "key")) == NULL)
+ {
+ g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_clear_object (&parser);
+ g_clear_object (&call);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+static gboolean
+ensure_credentials_sync (GoaProvider *provider,
+ GoaObject *object,
+ gint *out_expires_in,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GVariant *credentials;
+ GoaAccount *account;
+ const gchar *username;
+ gchar *password = NULL;
+ gboolean ret = FALSE;
+
+ credentials = goa_utils_lookup_credentials_sync (provider,
+ object,
+ cancellable,
+ error);
+ if (credentials == NULL)
+ {
+ if (error != NULL)
+ {
+ (*error)->domain = GOA_ERROR;
+ (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
+ }
+ goto out;
+ }
+
+ account = goa_object_peek_account (object);
+ username = goa_account_get_identity (account);
+
+ if (!g_variant_lookup (credentials, "password", "s", &password))
+ {
+ if (error != NULL)
+ {
+ *error = g_error_new (GOA_ERROR,
+ GOA_ERROR_NOT_AUTHORIZED,
+ _("Did not find password with identity ‘%s’ in credentials"),
+ username);
+ }
+ goto out;
+ }
+
+ ret = lastfm_login_sync (provider, username, password, error);
+ if (!ret)
+ {
+ if (error != NULL)
+ {
+ g_prefix_error (error,
+ /* Translators: the first %s is the username
+ * (eg., debarshi ray gmail com or rishi), and the
+ * (%s, %d) is the error domain and code.
+ */
+ _("Invalid password with username ‘%s’ (%s, %d): "),
+ username,
+ g_quark_to_string ((*error)->domain),
+ (*error)->code);
+ (*error)->domain = GOA_ERROR;
+ (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
+ }
+ goto out;
+ }
+
+ if (out_expires_in != NULL)
+ *out_expires_in = 0;
+
+ out:
+ g_free (password);
+ if (credentials != NULL)
+ g_variant_unref (credentials);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_entry (GtkWidget *grid,
+ gint row,
+ const gchar *text,
+ GtkWidget **out_entry)
+{
+ GtkStyleContext *context;
+ GtkWidget *label;
+ GtkWidget *entry;
+
+ label = gtk_label_new_with_mnemonic (text);
+ context = gtk_widget_get_style_context (label);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_DIM_LABEL);
+ gtk_widget_set_halign (label, GTK_ALIGN_END);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_grid_attach (GTK_GRID (grid), label, 0, row, 1, 1);
+
+ entry = gtk_entry_new ();
+ gtk_widget_set_hexpand (entry, TRUE);
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ gtk_grid_attach (GTK_GRID (grid), entry, 1, row, 3, 1);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+ if (out_entry != NULL)
+ *out_entry = entry;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ GCancellable *cancellable;
+
+ GtkDialog *dialog;
+ GMainLoop *loop;
+
+ GtkWidget *cluebar;
+ GtkWidget *cluebar_label;
+ GtkWidget *connect_button;
+ GtkWidget *progress_grid;
+
+ GtkWidget *username;
+ GtkWidget *password;
+
+ gchar *account_object_path;
+ gchar *access_token;
+
+ GError *error;
+} AddAccountData;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_username_or_password_changed (GtkEditable *editable, gpointer user_data)
+{
+ AddAccountData *data = user_data;
+ gboolean can_add;
+ gchar *username;
+ gchar *password;
+
+ can_add = FALSE;
+ username = NULL;
+ password = NULL;
+
+ username = g_strdup (gtk_entry_get_text (GTK_ENTRY (data->username)));
+ password = g_strdup (gtk_entry_get_text (GTK_ENTRY (data->password)));
+ if ((username == NULL) || (password == NULL))
+ goto out;
+
+ can_add = gtk_entry_get_text_length (GTK_ENTRY (data->username)) != 0
+ && gtk_entry_get_text_length (GTK_ENTRY (data->password)) != 0;
+
+ out:
+ gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, can_add);
+ g_free (username);
+ g_free (password);
+}
+
+static void
+create_account_details_ui (GoaProvider *provider,
+ GtkDialog *dialog,
+ GtkBox *vbox,
+ gboolean new_account,
+ AddAccountData *data)
+{
+ GtkWidget *grid0;
+ GtkWidget *grid1;
+ GtkWidget *label;
+ GtkWidget *spinner;
+ gint row;
+ gint width;
+
+ goa_utils_set_dialog_title (provider, dialog, new_account);
+
+ grid0 = gtk_grid_new ();
+ gtk_container_set_border_width (GTK_CONTAINER (grid0), 5);
+ gtk_widget_set_margin_bottom (grid0, 6);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (grid0), GTK_ORIENTATION_VERTICAL);
+ gtk_grid_set_row_spacing (GTK_GRID (grid0), 12);
+ gtk_container_add (GTK_CONTAINER (vbox), grid0);
+
+ data->cluebar = gtk_info_bar_new ();
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (data->cluebar), GTK_MESSAGE_ERROR);
+ gtk_widget_set_hexpand (data->cluebar, TRUE);
+ gtk_widget_set_no_show_all (data->cluebar, TRUE);
+ gtk_container_add (GTK_CONTAINER (grid0), data->cluebar);
+
+ data->cluebar_label = gtk_label_new ("");
+ gtk_label_set_line_wrap (GTK_LABEL (data->cluebar_label), TRUE);
+ gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (data->cluebar))),
+ data->cluebar_label);
+
+ grid1 = gtk_grid_new ();
+ gtk_grid_set_column_spacing (GTK_GRID (grid1), 12);
+ gtk_grid_set_row_spacing (GTK_GRID (grid1), 12);
+ gtk_container_add (GTK_CONTAINER (grid0), grid1);
+
+ row = 0;
+ add_entry (grid1, row++, _("User_name"), &data->username);
+ add_entry (grid1, row++, _("_Password"), &data->password);
+ gtk_entry_set_visibility (GTK_ENTRY (data->password), FALSE);
+
+ gtk_widget_grab_focus ((new_account) ? data->username : data->password);
+
+ g_signal_connect (data->username, "changed", G_CALLBACK (on_username_or_password_changed), data);
+ g_signal_connect (data->password, "changed", G_CALLBACK (on_username_or_password_changed), data);
+
+ gtk_dialog_add_button (data->dialog, _("_Cancel"), GTK_RESPONSE_CANCEL);
+ data->connect_button = gtk_dialog_add_button (data->dialog, _("C_onnect"), GTK_RESPONSE_OK);
+ gtk_dialog_set_default_response (data->dialog, GTK_RESPONSE_OK);
+ gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, FALSE);
+
+ data->progress_grid = gtk_grid_new ();
+ gtk_widget_set_no_show_all (data->progress_grid, TRUE);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (data->progress_grid), GTK_ORIENTATION_HORIZONTAL);
+ gtk_grid_set_column_spacing (GTK_GRID (data->progress_grid), 3);
+ gtk_container_add (GTK_CONTAINER (grid0), data->progress_grid);
+
+ spinner = gtk_spinner_new ();
+ gtk_widget_set_size_request (spinner, 20, 20);
+ gtk_widget_show (spinner);
+ gtk_spinner_start (GTK_SPINNER (spinner));
+ gtk_container_add (GTK_CONTAINER (data->progress_grid), spinner);
+
+ label = gtk_label_new (_("Connecting…"));
+ gtk_widget_show (label);
+ gtk_container_add (GTK_CONTAINER (data->progress_grid), label);
+
+ if (new_account)
+ {
+ gtk_window_get_size (GTK_WINDOW (data->dialog), &width, NULL);
+ gtk_widget_set_size_request (GTK_WIDGET (data->dialog), width, -1);
+ }
+ else
+ {
+ GtkWindow *parent;
+
+ /* Keep in sync with GoaPanelAddAccountDialog in
+ * gnome-control-center.
+ */
+ parent = gtk_window_get_transient_for (GTK_WINDOW (data->dialog));
+ if (parent != NULL)
+ {
+ gtk_window_get_size (parent, &width, NULL);
+ gtk_widget_set_size_request (GTK_WIDGET (data->dialog), (gint) (0.5 * width), -1);
+ }
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_account_cb (GoaManager *manager, GAsyncResult *res, gpointer user_data)
+{
+ AddAccountData *data = user_data;
+ goa_manager_call_add_account_finish (manager,
+ &data->account_object_path,
+ res,
+ &data->error);
+ g_main_loop_quit (data->loop);
+}
+
+static void
+check_cb (RestProxyCall *call,
+ const GError *error,
+ GObject *weak_object,
+ gpointer user_data)
+{
+ AddAccountData *data = user_data;
+ JsonNode *session;
+ JsonParser *parser;
+ JsonObject *json_obj;
+ JsonObject *session_obj;
+ const gchar *payload;
+
+ parser = NULL;
+
+ parser = json_parser_new ();
+ payload = rest_proxy_call_get_payload (call);
+
+ if (payload == NULL)
+ {
+ g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
+ goto out;
+ }
+
+ if (!json_parser_load_from_data (parser,
+ payload,
+ rest_proxy_call_get_payload_length (call),
+ &data->error))
+ {
+ g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
+ goto out;
+ }
+
+ json_obj = json_node_get_object (json_parser_get_root (parser));
+ session = json_object_get_member (json_obj, "session");
+ if (session == NULL)
+ {
+ g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Authentication failed"));
+ goto out;
+ }
+ session_obj = json_node_get_object (session);
+
+ if (g_strdup (json_object_get_string_member (session_obj, "name")) == NULL)
+ {
+ g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
+ goto out;
+ }
+
+ data->access_token = g_strdup (json_object_get_string_member (session_obj, "key"));
+ if (data->access_token == NULL)
+ {
+ g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
+ goto out;
+ }
+
+ out:
+ g_main_loop_quit (data->loop);
+ gtk_widget_set_sensitive (data->connect_button, TRUE);
+ gtk_widget_hide (data->progress_grid);
+ g_clear_object (&parser);
+}
+
+static void
+dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data)
+{
+ AddAccountData *data = user_data;
+
+ if (response_id == GTK_RESPONSE_CANCEL)
+ g_cancellable_cancel (data->cancellable);
+}
+
+static void
+on_rest_proxy_call_cancelled_cb (GCancellable *cancellable, RestProxyCall *call)
+{
+ rest_proxy_call_cancel (call);
+}
+
+static void
+lastfm_login (GoaProvider *provider,
+ const gchar *username,
+ const gchar *password,
+ GCancellable *cancellable,
+ RestProxyCallAsyncCallback callback,
+ gpointer user_data)
+{
+ AddAccountData *data = user_data;
+ RestProxyCall *call;
+ gchar *sig;
+ gchar *sig_md5;
+
+ call = NULL;
+
+ sig = g_strdup_printf ("api_key%s"
+ "methodauth.getMobileSession"
+ "password%s"
+ "username%s"
+ "%s",
+ GOA_LASTFM_CLIENT_ID,
+ password,
+ username,
+ GOA_LASTFM_CLIENT_SECRET);
+ sig_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig, -1);
+
+ call = rest_proxy_new_call (rest_proxy_new (get_request_uri (provider), FALSE));
+
+ rest_proxy_call_set_method (call, "POST");
+ rest_proxy_call_add_header (call, "Content-Type", "application/x-www-form-urlencoded");
+ rest_proxy_call_add_param (call, "method", "auth.getMobileSession");
+ rest_proxy_call_add_param (call, "api_key", GOA_LASTFM_CLIENT_ID);
+ rest_proxy_call_add_param (call, "username", username);
+ rest_proxy_call_add_param (call, "password", password);
+ rest_proxy_call_add_param (call, "api_sig", sig_md5);
+ rest_proxy_call_add_param (call, "format", "json");
+
+ rest_proxy_call_async (call, callback, NULL, data, &data->error);
+
+ g_signal_connect (cancellable, "cancelled", G_CALLBACK (on_rest_proxy_call_cancelled_cb), call);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GoaObject *
+add_account (GoaProvider *provider,
+ GoaClient *client,
+ GtkDialog *dialog,
+ GtkBox *vbox,
+ GError **error)
+{
+ AddAccountData data;
+ GVariantBuilder credentials;
+ GVariantBuilder details;
+ GoaObject *ret;
+ const gchar *password;
+ const gchar *username;
+ const gchar *provider_type;
+ gint response;
+
+ ret = NULL;
+
+ memset (&data, 0, sizeof (AddAccountData));
+ data.cancellable = g_cancellable_new ();
+ data.loop = g_main_loop_new (NULL, FALSE);
+ data.dialog = dialog;
+ data.error = NULL;
+
+ create_account_details_ui (provider, dialog, vbox, TRUE, &data);
+ gtk_widget_show_all (GTK_WIDGET (vbox));
+ g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data);
+
+ login_again:
+ response = gtk_dialog_run (dialog);
+ if (response != GTK_RESPONSE_OK)
+ {
+ g_set_error (&data.error, GOA_ERROR, GOA_ERROR_DIALOG_DISMISSED, _("Dialog was dismissed"));
+ goto out;
+ }
+
+ username = gtk_entry_get_text (GTK_ENTRY (data.username));
+ password = gtk_entry_get_text (GTK_ENTRY (data.password));
+
+ /* See if there's already an account of this type with the
+ * given identity
+ */
+ provider_type = goa_provider_get_provider_type (provider);
+ if (!goa_utils_check_duplicate (client,
+ username,
+ username,
+ provider_type,
+ (GoaPeekInterfaceFunc) goa_object_peek_oauth2_based,
+ &data.error))
+ goto out;
+
+ g_cancellable_reset (data.cancellable);
+ lastfm_login (provider,
+ username,
+ password,
+ data.cancellable,
+ (RestProxyCallAsyncCallback) check_cb,
+ &data);
+
+ gtk_widget_set_sensitive (data.connect_button, FALSE);
+ gtk_widget_show (data.progress_grid);
+ g_main_loop_run (data.loop);
+
+ if (g_cancellable_is_cancelled (data.cancellable))
+ {
+ g_prefix_error (&data.error,
+ _("Dialog was dismissed (%s, %d): "),
+ g_quark_to_string (data.error->domain),
+ data.error->code);
+ data.error->domain = GOA_ERROR;
+ data.error->code = GOA_ERROR_DIALOG_DISMISSED;
+ g_message ("%s", data.error->message);
+ goto out;
+ }
+ else if (data.error != NULL)
+ {
+ gchar *markup;
+
+ gtk_button_set_label (GTK_BUTTON (data.connect_button), _("_Try Again"));
+
+ markup = g_strdup_printf ("<b>%s:</b>\n%s", _("Error connecting to Last.fm"), data.error->message);
+ g_clear_error (&data.error);
+
+ gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+ g_free (markup);
+
+ gtk_widget_set_no_show_all (data.cluebar, FALSE);
+ gtk_widget_show_all (data.cluebar);
+ goto login_again;
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+
+ g_variant_builder_init (&credentials, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add (&credentials, "{sv}", "password", g_variant_new_string (password));
+ g_variant_builder_add (&credentials, "{sv}", "access_token", g_variant_new_string (data.access_token));
+
+ g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
+ g_variant_builder_add (&details, "{ss}", "MusicEnabled", "true");
+
+ /* OK, everything is dandy, add the account */
+ /* we want the GoaClient to update before this method returns (so it
+ * can create a proxy for the new object) so run the mainloop while
+ * waiting for this to complete
+ */
+ goa_manager_call_add_account (goa_client_get_manager (client),
+ goa_provider_get_provider_type (provider),
+ username,
+ username,
+ g_variant_builder_end (&credentials),
+ g_variant_builder_end (&details),
+ NULL, /* GCancellable* */
+ (GAsyncReadyCallback) add_account_cb,
+ &data);
+ g_main_loop_run (data.loop);
+ if (data.error != NULL)
+ goto out;
+
+ ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client),
+ data.account_object_path));
+
+ out:
+ /* We might have an object even when data.error is set.
+ * eg., if we failed to store the credentials in the keyring.
+ */
+ if (data.error != NULL)
+ g_propagate_error (error, data.error);
+ else
+ g_assert (ret != NULL);
+
+ g_free (data.account_object_path);
+ if (data.loop != NULL)
+ g_main_loop_unref (data.loop);
+ g_clear_object (&data.cancellable);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+refresh_account (GoaProvider *provider,
+ GoaClient *client,
+ GoaObject *object,
+ GtkWindow *parent,
+ GError **error)
+{
+ AddAccountData data;
+ GVariantBuilder builder;
+ GoaAccount *account;
+ GtkWidget *dialog;
+ GtkWidget *vbox;
+ gboolean ret;
+ const gchar *password;
+ const gchar *username;
+ gint response;
+
+ g_return_val_if_fail (GOA_IS_LASTFM_PROVIDER (provider), FALSE);
+ g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE);
+ g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ ret = FALSE;
+
+ dialog = gtk_dialog_new_with_buttons (NULL,
+ parent,
+ GTK_DIALOG_MODAL
+ | GTK_DIALOG_DESTROY_WITH_PARENT
+ | GTK_DIALOG_USE_HEADER_BAR,
+ NULL,
+ NULL);
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+ gtk_box_set_spacing (GTK_BOX (vbox), 12);
+
+ memset (&data, 0, sizeof (AddAccountData));
+ data.cancellable = g_cancellable_new ();
+ data.loop = g_main_loop_new (NULL, FALSE);
+ data.dialog = GTK_DIALOG (dialog);
+ data.error = NULL;
+
+ create_account_details_ui (provider, GTK_DIALOG (dialog), GTK_BOX (vbox), FALSE, &data);
+
+ account = goa_object_peek_account (object);
+ username = goa_account_get_identity (account);
+ gtk_entry_set_text (GTK_ENTRY (data.username), username);
+ gtk_editable_set_editable (GTK_EDITABLE (data.username), FALSE);
+
+ gtk_widget_show_all (dialog);
+ g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data);
+
+ login_again:
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+ if (response != GTK_RESPONSE_OK)
+ {
+ g_set_error (&data.error, GOA_ERROR, GOA_ERROR_DIALOG_DISMISSED, _("Dialog was dismissed"));
+ goto out;
+ }
+
+ password = gtk_entry_get_text (GTK_ENTRY (data.password));
+ g_cancellable_reset (data.cancellable);
+ lastfm_login (provider,
+ username,
+ password,
+ data.cancellable,
+ (RestProxyCallAsyncCallback) check_cb,
+ &data);
+ gtk_widget_set_sensitive (data.connect_button, FALSE);
+ gtk_widget_show (data.progress_grid);
+ g_main_loop_run (data.loop);
+
+ if (g_cancellable_is_cancelled (data.cancellable))
+ {
+ g_prefix_error (&data.error,
+ _("Dialog was dismissed (%s, %d): "),
+ g_quark_to_string (data.error->domain),
+ data.error->code);
+ data.error->domain = GOA_ERROR;
+ data.error->code = GOA_ERROR_DIALOG_DISMISSED;
+ goto out;
+ }
+ else if (data.error != NULL)
+ {
+ gchar *markup;
+
+ markup = g_strdup_printf ("<b>%s:</b>\n%s", _("Error connecting to Last.fm"), data.error->message);
+ g_clear_error (&data.error);
+
+ gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
+ g_free (markup);
+
+ gtk_button_set_label (GTK_BUTTON (data.connect_button), _("_Try Again"));
+ gtk_widget_set_no_show_all (data.cluebar, FALSE);
+ gtk_widget_show_all (data.cluebar);
+ goto login_again;
+ }
+
+ /* TODO: run in worker thread */
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add (&builder, "{sv}", "password", g_variant_new_string (password));
+ g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (data.access_token));
+
+ if (!goa_utils_store_credentials_for_object_sync (provider,
+ object,
+ g_variant_builder_end (&builder),
+ NULL, /* GCancellable */
+ &data.error))
+ goto out;
+
+ goa_account_call_ensure_credentials (account,
+ NULL, /* GCancellable */
+ NULL, NULL); /* callback, user_data */
+
+ ret = TRUE;
+ out:
+ if (data.error != NULL)
+ g_propagate_error (error, data.error);
+
+ gtk_widget_destroy (dialog);
+ if (data.loop != NULL)
+ g_main_loop_unref (data.loop);
+ g_clear_object (&data.cancellable);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+goa_lastfm_provider_init (GoaLastfmProvider *provider)
+{
+}
+
+static void
+goa_lastfm_provider_class_init (GoaLastfmProviderClass *klass)
+{
+ GoaProviderClass *provider_class;
+ GoaOAuth2ProviderClass *oauth2_class = GOA_OAUTH2_PROVIDER_CLASS (klass);
+
+ provider_class = GOA_PROVIDER_CLASS (klass);
+ provider_class->get_provider_type = get_provider_type;
+ provider_class->get_provider_name = get_provider_name;
+ provider_class->get_provider_group = get_provider_group;
+ provider_class->get_provider_features = get_provider_features;
+ provider_class->add_account = add_account;
+ provider_class->refresh_account = refresh_account;
+ provider_class->build_object = build_object;
+ provider_class->ensure_credentials_sync = ensure_credentials_sync;
+
+ oauth2_class->get_client_id = get_client_id;
+ oauth2_class->get_client_secret = get_client_secret;
+}
diff --git a/src/goabackend/goalastfmprovider.h b/src/goabackend/goalastfmprovider.h
new file mode 100644
index 0000000..58fab14
--- /dev/null
+++ b/src/goabackend/goalastfmprovider.h
@@ -0,0 +1,40 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2015 Felipe Borges
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION)
+#error "Only <goabackend/goabackend.h> can be included directly."
+#endif
+
+#ifndef __GOA_LASTFM_PROVIDER_H__
+#define __GOA_LASTFM_PROVIDER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GOA_TYPE_LASTFM_PROVIDER (goa_lastfm_provider_get_type ())
+#define GOA_LASTFM_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOA_TYPE_LASTFM_PROVIDER,
GoaLastfmProvider))
+#define GOA_IS_LASTFM_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOA_TYPE_LASTFM_PROVIDER))
+
+typedef struct _GoaLastfmProvider GoaLastfmProvider;
+
+GType goa_lastfm_provider_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GOA_LASTFM_PROVIDER_H__ */
diff --git a/src/goabackend/goaprovider.c b/src/goabackend/goaprovider.c
index e555e4c..5c7d5a6 100644
--- a/src/goabackend/goaprovider.c
+++ b/src/goabackend/goaprovider.c
@@ -34,6 +34,7 @@
#include "goatelepathyfactory.h"
#include "goapocketprovider.h"
#include "goamediaserverprovider.h"
+#include "goalastfmprovider.h"
#ifdef GOA_KERBEROS_ENABLED
#include "goakerberosprovider.h"
@@ -906,6 +907,9 @@ static struct
#ifdef GOA_EXCHANGE_ENABLED
{ GOA_EXCHANGE_NAME, goa_exchange_provider_get_type },
#endif
+#ifdef GOA_LASTFM_ENABLED
+ { GOA_LASTFM_NAME, goa_lastfm_provider_get_type },
+#endif
#ifdef GOA_IMAP_SMTP_ENABLED
{ GOA_IMAP_SMTP_NAME, goa_imap_smtp_provider_get_type },
#endif
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]