1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * Copyright © 2015 Felipe Borges
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "config.h"
20 #include <glib/gi18n-lib.h>
21
22 #include <rest/rest-proxy.h>
23 #include <json-glib/json-glib.h>
24
25 #include "goahttpclient.h"
26 #include "goaprovider.h"
27 #include "goaprovider-priv.h"
28 #include "goaoauth2provider.h"
29 #include "goalastfmprovider.h"
30 #include "goarestproxy.h"
31 #include "goautils.h"
32
33 struct _GoaLastfmProvider
34 {
35 GoaOAuth2Provider parent_instance;
36 };
37
38 G_DEFINE_TYPE_WITH_CODE (GoaLastfmProvider, goa_lastfm_provider, GOA_TYPE_OAUTH2_PROVIDER,
39 goa_provider_ensure_extension_points_registered ();
40 g_io_extension_point_implement (GOA_PROVIDER_EXTENSION_POINT_NAME,
41 g_define_type_id,
42 GOA_LASTFM_NAME,
43 0));
44
45 /* ---------------------------------------------------------------------------------------------------- */
46
47 static const gchar *
get_provider_type(GoaProvider * _provider)48 get_provider_type (GoaProvider *_provider)
49 {
50 return GOA_LASTFM_NAME;
51 }
52
53 static gchar *
get_provider_name(GoaProvider * _provider,GoaObject * object)54 get_provider_name (GoaProvider *_provider,
55 GoaObject *object)
56 {
57 return g_strdup (_("Last.fm"));
58 }
59
60 static GoaProviderGroup
get_provider_group(GoaProvider * _provider)61 get_provider_group (GoaProvider *_provider)
62 {
63 return GOA_PROVIDER_GROUP_BRANDED;
64 }
65
66 static GoaProviderFeatures
get_provider_features(GoaProvider * _provider)67 get_provider_features (GoaProvider *_provider)
68 {
69 return GOA_PROVIDER_FEATURE_BRANDED | GOA_PROVIDER_FEATURE_MUSIC;
70 }
71
72 static const gchar *
get_request_uri(GoaProvider * provider)73 get_request_uri (GoaProvider *provider)
74 {
75 return "https://ws.audioscrobbler.com/2.0/";
76 }
77
78 static const gchar *
get_client_id(GoaOAuth2Provider * provider)79 get_client_id (GoaOAuth2Provider *provider)
80 {
81 return GOA_LASTFM_CLIENT_ID;
82 }
83
84 static const gchar *
get_client_secret(GoaOAuth2Provider * provider)85 get_client_secret (GoaOAuth2Provider *provider)
86 {
87 return GOA_LASTFM_CLIENT_SECRET;
88 }
89
90 /* ---------------------------------------------------------------------------------------------------- */
91
92 static gboolean
build_object(GoaProvider * provider,GoaObjectSkeleton * object,GKeyFile * key_file,const gchar * group,GDBusConnection * connection,gboolean just_added,GError ** error)93 build_object (GoaProvider *provider,
94 GoaObjectSkeleton *object,
95 GKeyFile *key_file,
96 const gchar *group,
97 GDBusConnection *connection,
98 gboolean just_added,
99 GError **error)
100 {
101 GoaAccount *account;
102 GoaMusic *music = NULL;
103 gboolean music_enabled;
104 gboolean ret = FALSE;
105
106 account = NULL;
107
108 /* Chain up */
109 if (!GOA_PROVIDER_CLASS (goa_lastfm_provider_parent_class)->build_object (provider,
110 object,
111 key_file,
112 group,
113 connection,
114 just_added,
115 error))
116 goto out;
117
118 account = goa_object_get_account (GOA_OBJECT (object));
119
120 /* Music */
121 music = goa_object_get_music (GOA_OBJECT (object));
122 music_enabled = g_key_file_get_boolean (key_file, group, "MusicEnabled", NULL);
123 if (music_enabled)
124 {
125 if (music == NULL)
126 {
127 music = goa_music_skeleton_new ();
128 goa_object_skeleton_set_music (object, music);
129 }
130 }
131 else
132 {
133 if (music != NULL)
134 goa_object_skeleton_set_music (object, NULL);
135 }
136
137 if (just_added)
138 {
139 goa_account_set_music_disabled (account, !music_enabled);
140
141 g_signal_connect (account,
142 "notify::music-disabled",
143 G_CALLBACK (goa_util_account_notify_property_cb),
144 (gpointer) "MusicEnabled");
145 }
146
147 ret = TRUE;
148
149 out:
150 g_clear_object (&music);
151 g_clear_object (&account);
152 return ret;
153 }
154
155 static gboolean
lastfm_login_sync(GoaProvider * provider,const gchar * username,const gchar * password,GError ** error)156 lastfm_login_sync (GoaProvider *provider,
157 const gchar *username,
158 const gchar *password,
159 GError **error)
160 {
161 JsonParser *parser;
162 JsonObject *json_obj;
163 JsonObject *session_obj;
164 JsonNode *root;
165 RestProxyCall *call;
166 const gchar *payload;
167 gchar *sig;
168 gchar *sig_md5;
169 gboolean ret;
170
171 call = NULL;
172 parser = NULL;
173 ret = FALSE;
174
175 sig = g_strdup_printf ("api_key%s"
176 "methodauth.getMobileSession"
177 "password%s"
178 "username%s"
179 "%s",
180 GOA_LASTFM_CLIENT_ID,
181 password,
182 username,
183 GOA_LASTFM_CLIENT_SECRET);
184 sig_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig, -1);
185
186 call = rest_proxy_new_call (goa_rest_proxy_new (get_request_uri (provider), FALSE));
187
188 rest_proxy_call_set_method (call, "POST");
189 rest_proxy_call_add_header (call, "Content-Type", "application/x-www-form-urlencoded");
190 rest_proxy_call_add_param (call, "method", "auth.getMobileSession");
191 rest_proxy_call_add_param (call, "api_key", GOA_LASTFM_CLIENT_ID);
192 rest_proxy_call_add_param (call, "username", username);
193 rest_proxy_call_add_param (call, "password", password);
194 rest_proxy_call_add_param (call, "api_sig", sig_md5);
195 rest_proxy_call_add_param (call, "format", "json");
196
197 if (!rest_proxy_call_sync (call, error))
198 goto out;
199
200 parser = json_parser_new ();
201 payload = rest_proxy_call_get_payload (call);
202 if (payload == NULL)
203 {
204 g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
205 goto out;
206 }
207
208 if (!json_parser_load_from_data (parser,
209 payload,
210 rest_proxy_call_get_payload_length (call),
211 NULL))
212 {
213 g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
214 goto out;
215 }
216
217 root = json_parser_get_root (parser);
218 json_obj = json_node_get_object (root);
219 if (!json_object_has_member (json_obj, "session"))
220 {
221 g_warning ("Did not find session in JSON data");
222 g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
223 goto out;
224 }
225
226 session_obj = json_node_get_object (json_object_get_member (json_obj, "session"));
227 if (!json_object_has_member (session_obj, "name"))
228 {
229 g_warning ("Did not find session.name in JSON data");
230 g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
231 goto out;
232 }
233 if (!json_object_has_member (session_obj, "key"))
234 {
235 g_warning ("Did not find session.key in JSON data");
236 g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
237 goto out;
238 }
239
240 ret = TRUE;
241
242 out:
243 g_clear_object (&parser);
244 g_clear_object (&call);
245 return ret;
246 }
247
248 /* ---------------------------------------------------------------------------------------------------- */
249 static gboolean
ensure_credentials_sync(GoaProvider * provider,GoaObject * object,gint * out_expires_in,GCancellable * cancellable,GError ** error)250 ensure_credentials_sync (GoaProvider *provider,
251 GoaObject *object,
252 gint *out_expires_in,
253 GCancellable *cancellable,
254 GError **error)
255 {
256 gchar *username = NULL;
257 gchar *password = NULL;
258 gboolean ret = FALSE;
259
260 if (!goa_utils_get_credentials (provider, object, "password", &username, &password, cancellable, error))
261 {
262 if (error != NULL)
263 {
264 (*error)->domain = GOA_ERROR;
265 (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
266 }
267 goto out;
268 }
269
270 if (!lastfm_login_sync (provider, username, password, error))
271 {
272 if (error != NULL)
273 {
274 g_prefix_error (error,
275 /* Translators: the first %s is the username
276 * (eg., debarshi.ray@gmail.com or rishi), and the
277 * (%s, %d) is the error domain and code.
278 */
279 _("Invalid password with username “%s” (%s, %d): "),
280 username,
281 g_quark_to_string ((*error)->domain),
282 (*error)->code);
283 (*error)->domain = GOA_ERROR;
284 (*error)->code = GOA_ERROR_NOT_AUTHORIZED;
285 }
286 goto out;
287 }
288
289 if (out_expires_in != NULL)
290 *out_expires_in = 0;
291
292 ret = TRUE;
293
294 out:
295 g_free (username);
296 g_free (password);
297 return ret;
298 }
299
300 /* ---------------------------------------------------------------------------------------------------- */
301
302 static void
add_entry(GtkWidget * grid,gint row,const gchar * text,GtkWidget ** out_entry)303 add_entry (GtkWidget *grid,
304 gint row,
305 const gchar *text,
306 GtkWidget **out_entry)
307 {
308 GtkStyleContext *context;
309 GtkWidget *label;
310 GtkWidget *entry;
311
312 label = gtk_label_new_with_mnemonic (text);
313 context = gtk_widget_get_style_context (label);
314 gtk_style_context_add_class (context, GTK_STYLE_CLASS_DIM_LABEL);
315 gtk_widget_set_halign (label, GTK_ALIGN_END);
316 gtk_widget_set_hexpand (label, TRUE);
317 gtk_grid_attach (GTK_GRID (grid), label, 0, row, 1, 1);
318
319 entry = gtk_entry_new ();
320 gtk_widget_set_hexpand (entry, TRUE);
321 gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
322 gtk_grid_attach (GTK_GRID (grid), entry, 1, row, 3, 1);
323
324 gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
325 if (out_entry != NULL)
326 *out_entry = entry;
327 }
328
329 /* ---------------------------------------------------------------------------------------------------- */
330
331 typedef struct
332 {
333 GCancellable *cancellable;
334
335 GtkDialog *dialog;
336 GMainLoop *loop;
337
338 GtkWidget *cluebar;
339 GtkWidget *cluebar_label;
340 GtkWidget *connect_button;
341 GtkWidget *progress_grid;
342
343 GtkWidget *username;
344 GtkWidget *password;
345
346 gchar *account_object_path;
347 gchar *access_token;
348
349 GError *error;
350 } AddAccountData;
351
352 /* ---------------------------------------------------------------------------------------------------- */
353
354 static void
on_username_or_password_changed(GtkEditable * editable,gpointer user_data)355 on_username_or_password_changed (GtkEditable *editable, gpointer user_data)
356 {
357 AddAccountData *data = user_data;
358 gboolean can_add;
359 gchar *username;
360 gchar *password;
361
362 can_add = FALSE;
363 username = NULL;
364 password = NULL;
365
366 username = g_strdup (gtk_entry_get_text (GTK_ENTRY (data->username)));
367 password = g_strdup (gtk_entry_get_text (GTK_ENTRY (data->password)));
368 if ((username == NULL) || (password == NULL))
369 goto out;
370
371 can_add = gtk_entry_get_text_length (GTK_ENTRY (data->username)) != 0
372 && gtk_entry_get_text_length (GTK_ENTRY (data->password)) != 0;
373
374 out:
375 gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, can_add);
376 g_free (username);
377 g_free (password);
378 }
379
380 static void
create_account_details_ui(GoaProvider * provider,GtkDialog * dialog,GtkBox * vbox,gboolean new_account,AddAccountData * data)381 create_account_details_ui (GoaProvider *provider,
382 GtkDialog *dialog,
383 GtkBox *vbox,
384 gboolean new_account,
385 AddAccountData *data)
386 {
387 GtkWidget *grid0;
388 GtkWidget *grid1;
389 GtkWidget *label;
390 GtkWidget *spinner;
391 gint row;
392 gint width;
393
394 goa_utils_set_dialog_title (provider, dialog, new_account);
395
396 grid0 = gtk_grid_new ();
397 gtk_container_set_border_width (GTK_CONTAINER (grid0), 5);
398 gtk_widget_set_margin_bottom (grid0, 6);
399 gtk_orientable_set_orientation (GTK_ORIENTABLE (grid0), GTK_ORIENTATION_VERTICAL);
400 gtk_grid_set_row_spacing (GTK_GRID (grid0), 12);
401 gtk_container_add (GTK_CONTAINER (vbox), grid0);
402
403 data->cluebar = gtk_info_bar_new ();
404 gtk_info_bar_set_message_type (GTK_INFO_BAR (data->cluebar), GTK_MESSAGE_ERROR);
405 gtk_widget_set_hexpand (data->cluebar, TRUE);
406 gtk_widget_set_no_show_all (data->cluebar, TRUE);
407 gtk_container_add (GTK_CONTAINER (grid0), data->cluebar);
408
409 data->cluebar_label = gtk_label_new ("");
410 gtk_label_set_line_wrap (GTK_LABEL (data->cluebar_label), TRUE);
411 gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (data->cluebar))),
412 data->cluebar_label);
413
414 grid1 = gtk_grid_new ();
415 gtk_grid_set_column_spacing (GTK_GRID (grid1), 12);
416 gtk_grid_set_row_spacing (GTK_GRID (grid1), 12);
417 gtk_container_add (GTK_CONTAINER (grid0), grid1);
418
419 row = 0;
420 add_entry (grid1, row++, _("User_name"), &data->username);
421 add_entry (grid1, row++, _("_Password"), &data->password);
422 gtk_entry_set_visibility (GTK_ENTRY (data->password), FALSE);
423
424 gtk_widget_grab_focus ((new_account) ? data->username : data->password);
425
426 g_signal_connect (data->username, "changed", G_CALLBACK (on_username_or_password_changed), data);
427 g_signal_connect (data->password, "changed", G_CALLBACK (on_username_or_password_changed), data);
428
429 gtk_dialog_add_button (data->dialog, _("_Cancel"), GTK_RESPONSE_CANCEL);
430 data->connect_button = gtk_dialog_add_button (data->dialog, _("C_onnect"), GTK_RESPONSE_OK);
431 gtk_dialog_set_default_response (data->dialog, GTK_RESPONSE_OK);
432 gtk_dialog_set_response_sensitive (data->dialog, GTK_RESPONSE_OK, FALSE);
433
434 data->progress_grid = gtk_grid_new ();
435 gtk_widget_set_no_show_all (data->progress_grid, TRUE);
436 gtk_orientable_set_orientation (GTK_ORIENTABLE (data->progress_grid), GTK_ORIENTATION_HORIZONTAL);
437 gtk_grid_set_column_spacing (GTK_GRID (data->progress_grid), 3);
438 gtk_container_add (GTK_CONTAINER (grid0), data->progress_grid);
439
440 spinner = gtk_spinner_new ();
441 gtk_widget_set_size_request (spinner, 20, 20);
442 gtk_widget_show (spinner);
443 gtk_spinner_start (GTK_SPINNER (spinner));
444 gtk_container_add (GTK_CONTAINER (data->progress_grid), spinner);
445
446 label = gtk_label_new (_("Connecting…"));
447 gtk_widget_show (label);
448 gtk_container_add (GTK_CONTAINER (data->progress_grid), label);
449
450 if (new_account)
451 {
452 gtk_window_get_size (GTK_WINDOW (data->dialog), &width, NULL);
453 gtk_window_set_default_size (GTK_WINDOW (data->dialog), width, -1);
454 }
455 else
456 {
457 GtkWindow *parent;
458
459 /* Keep in sync with GoaPanelAddAccountDialog in
460 * gnome-control-center.
461 */
462 parent = gtk_window_get_transient_for (GTK_WINDOW (data->dialog));
463 if (parent != NULL)
464 {
465 gtk_window_get_size (parent, &width, NULL);
466 gtk_window_set_default_size (GTK_WINDOW (data->dialog), (gint) (0.5 * width), -1);
467 }
468 }
469 }
470
471 /* ---------------------------------------------------------------------------------------------------- */
472
473 static void
add_account_cb(GoaManager * manager,GAsyncResult * res,gpointer user_data)474 add_account_cb (GoaManager *manager, GAsyncResult *res, gpointer user_data)
475 {
476 AddAccountData *data = user_data;
477 goa_manager_call_add_account_finish (manager,
478 &data->account_object_path,
479 res,
480 &data->error);
481 g_main_loop_quit (data->loop);
482 }
483
484 static void
check_cb(RestProxyCall * call,const GError * error,GObject * weak_object,gpointer user_data)485 check_cb (RestProxyCall *call,
486 const GError *error,
487 GObject *weak_object,
488 gpointer user_data)
489 {
490 AddAccountData *data = user_data;
491 JsonNode *session;
492 JsonParser *parser;
493 JsonObject *json_obj;
494 JsonObject *session_obj;
495 const gchar *payload;
496
497 parser = NULL;
498
499 parser = json_parser_new ();
500 payload = rest_proxy_call_get_payload (call);
501
502 if (payload == NULL)
503 {
504 g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
505 goto out;
506 }
507
508 if (!json_parser_load_from_data (parser,
509 payload,
510 rest_proxy_call_get_payload_length (call),
511 &data->error))
512 {
513 g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
514 goto out;
515 }
516
517 json_obj = json_node_get_object (json_parser_get_root (parser));
518 if (!json_object_has_member (json_obj, "session"))
519 {
520 g_warning ("Did not find session in JSON data");
521 g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Authentication failed"));
522 goto out;
523 }
524
525 session = json_object_get_member (json_obj, "session");
526 session_obj = json_node_get_object (session);
527 if (!json_object_has_member (session_obj, "name"))
528 {
529 g_warning ("Did not find session.name in JSON data");
530 g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
531 goto out;
532 }
533 if (!json_object_has_member (session_obj, "key"))
534 {
535 g_warning ("Did not find session.key in JSON data");
536 g_set_error (&data->error, GOA_ERROR, GOA_ERROR_FAILED, _("Could not parse response"));
537 goto out;
538 }
539
540 data->access_token = g_strdup (json_object_get_string_member (session_obj, "key"));
541
542 out:
543 g_main_loop_quit (data->loop);
544 gtk_widget_set_sensitive (data->connect_button, TRUE);
545 gtk_widget_hide (data->progress_grid);
546 g_clear_object (&parser);
547 }
548
549 static void
dialog_response_cb(GtkDialog * dialog,gint response_id,gpointer user_data)550 dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data)
551 {
552 AddAccountData *data = user_data;
553
554 if (response_id == GTK_RESPONSE_CANCEL)
555 g_cancellable_cancel (data->cancellable);
556 }
557
558 static void
on_rest_proxy_call_cancelled_cb(GCancellable * cancellable,RestProxyCall * call)559 on_rest_proxy_call_cancelled_cb (GCancellable *cancellable, RestProxyCall *call)
560 {
561 rest_proxy_call_cancel (call);
562 }
563
564 static void
lastfm_login(GoaProvider * provider,const gchar * username,const gchar * password,GCancellable * cancellable,RestProxyCallAsyncCallback callback,gpointer user_data)565 lastfm_login (GoaProvider *provider,
566 const gchar *username,
567 const gchar *password,
568 GCancellable *cancellable,
569 RestProxyCallAsyncCallback callback,
570 gpointer user_data)
571 {
572 AddAccountData *data = user_data;
573 RestProxyCall *call;
574 gchar *sig;
575 gchar *sig_md5;
576
577 call = NULL;
578
579 sig = g_strdup_printf ("api_key%s"
580 "methodauth.getMobileSession"
581 "password%s"
582 "username%s"
583 "%s",
584 GOA_LASTFM_CLIENT_ID,
585 password,
586 username,
587 GOA_LASTFM_CLIENT_SECRET);
588 sig_md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, sig, -1);
589
590 call = rest_proxy_new_call (goa_rest_proxy_new (get_request_uri (provider), FALSE));
591
592 rest_proxy_call_set_method (call, "POST");
593 rest_proxy_call_add_header (call, "Content-Type", "application/x-www-form-urlencoded");
594 rest_proxy_call_add_param (call, "method", "auth.getMobileSession");
595 rest_proxy_call_add_param (call, "api_key", GOA_LASTFM_CLIENT_ID);
596 rest_proxy_call_add_param (call, "username", username);
597 rest_proxy_call_add_param (call, "password", password);
598 rest_proxy_call_add_param (call, "api_sig", sig_md5);
599 rest_proxy_call_add_param (call, "format", "json");
600
601 rest_proxy_call_async (call, callback, NULL, data, &data->error);
602
603 g_signal_connect (cancellable, "cancelled", G_CALLBACK (on_rest_proxy_call_cancelled_cb), call);
604
605 g_free (sig_md5);
606 g_free (sig);
607 g_object_unref (call);
608 }
609
610 /* ---------------------------------------------------------------------------------------------------- */
611
612 static GoaObject *
add_account(GoaProvider * provider,GoaClient * client,GtkDialog * dialog,GtkBox * vbox,GError ** error)613 add_account (GoaProvider *provider,
614 GoaClient *client,
615 GtkDialog *dialog,
616 GtkBox *vbox,
617 GError **error)
618 {
619 AddAccountData data;
620 GVariantBuilder credentials;
621 GVariantBuilder details;
622 GoaObject *ret;
623 const gchar *password;
624 const gchar *username;
625 const gchar *provider_type;
626 gint response;
627
628 ret = NULL;
629
630 memset (&data, 0, sizeof (AddAccountData));
631 data.cancellable = g_cancellable_new ();
632 data.loop = g_main_loop_new (NULL, FALSE);
633 data.dialog = dialog;
634 data.error = NULL;
635
636 create_account_details_ui (provider, dialog, vbox, TRUE, &data);
637 gtk_widget_show_all (GTK_WIDGET (vbox));
638 g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data);
639
640 login_again:
641 response = gtk_dialog_run (dialog);
642 if (response != GTK_RESPONSE_OK)
643 {
644 g_set_error (&data.error, GOA_ERROR, GOA_ERROR_DIALOG_DISMISSED, _("Dialog was dismissed"));
645 goto out;
646 }
647
648 username = gtk_entry_get_text (GTK_ENTRY (data.username));
649 password = gtk_entry_get_text (GTK_ENTRY (data.password));
650
651 /* See if there's already an account of this type with the
652 * given identity
653 */
654 provider_type = goa_provider_get_provider_type (provider);
655 if (!goa_utils_check_duplicate (client,
656 username,
657 username,
658 provider_type,
659 (GoaPeekInterfaceFunc) goa_object_peek_oauth2_based,
660 &data.error))
661 goto out;
662
663 g_cancellable_reset (data.cancellable);
664 lastfm_login (provider,
665 username,
666 password,
667 data.cancellable,
668 (RestProxyCallAsyncCallback) check_cb,
669 &data);
670
671 gtk_widget_set_sensitive (data.connect_button, FALSE);
672 gtk_widget_show (data.progress_grid);
673 g_main_loop_run (data.loop);
674
675 if (g_cancellable_is_cancelled (data.cancellable))
676 {
677 g_prefix_error (&data.error,
678 _("Dialog was dismissed (%s, %d): "),
679 g_quark_to_string (data.error->domain),
680 data.error->code);
681 data.error->domain = GOA_ERROR;
682 data.error->code = GOA_ERROR_DIALOG_DISMISSED;
683 g_message ("%s", data.error->message);
684 goto out;
685 }
686 else if (data.error != NULL)
687 {
688 gchar *markup;
689
690 gtk_button_set_label (GTK_BUTTON (data.connect_button), _("_Try Again"));
691
692 markup = g_strdup_printf ("<b>%s:</b>\n%s", _("Error connecting to Last.fm"), data.error->message);
693 g_clear_error (&data.error);
694
695 gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
696 g_free (markup);
697
698 gtk_widget_set_no_show_all (data.cluebar, FALSE);
699 gtk_widget_show_all (data.cluebar);
700 goto login_again;
701 }
702
703 gtk_widget_hide (GTK_WIDGET (dialog));
704
705 g_variant_builder_init (&credentials, G_VARIANT_TYPE_VARDICT);
706 g_variant_builder_add (&credentials, "{sv}", "password", g_variant_new_string (password));
707 g_variant_builder_add (&credentials, "{sv}", "access_token", g_variant_new_string (data.access_token));
708
709 g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
710 g_variant_builder_add (&details, "{ss}", "MusicEnabled", "true");
711
712 /* OK, everything is dandy, add the account */
713 /* we want the GoaClient to update before this method returns (so it
714 * can create a proxy for the new object) so run the mainloop while
715 * waiting for this to complete
716 */
717 goa_manager_call_add_account (goa_client_get_manager (client),
718 goa_provider_get_provider_type (provider),
719 username,
720 username,
721 g_variant_builder_end (&credentials),
722 g_variant_builder_end (&details),
723 NULL, /* GCancellable* */
724 (GAsyncReadyCallback) add_account_cb,
725 &data);
726 g_main_loop_run (data.loop);
727 if (data.error != NULL)
728 goto out;
729
730 ret = GOA_OBJECT (g_dbus_object_manager_get_object (goa_client_get_object_manager (client),
731 data.account_object_path));
732
733 out:
734 /* We might have an object even when data.error is set.
735 * eg., if we failed to store the credentials in the keyring.
736 */
737 if (data.error != NULL)
738 g_propagate_error (error, data.error);
739 else
740 g_assert (ret != NULL);
741
742 g_signal_handlers_disconnect_by_func (dialog, dialog_response_cb, &data);
743
744 g_free (data.access_token);
745 g_free (data.account_object_path);
746 g_clear_pointer (&data.loop, g_main_loop_unref);
747 g_clear_object (&data.cancellable);
748 return ret;
749 }
750
751 /* ---------------------------------------------------------------------------------------------------- */
752
753 static gboolean
refresh_account(GoaProvider * provider,GoaClient * client,GoaObject * object,GtkWindow * parent,GError ** error)754 refresh_account (GoaProvider *provider,
755 GoaClient *client,
756 GoaObject *object,
757 GtkWindow *parent,
758 GError **error)
759 {
760 AddAccountData data;
761 GVariantBuilder builder;
762 GoaAccount *account;
763 GtkWidget *dialog;
764 GtkWidget *vbox;
765 gboolean ret;
766 const gchar *password;
767 const gchar *username;
768 gint response;
769
770 g_return_val_if_fail (GOA_IS_LASTFM_PROVIDER (provider), FALSE);
771 g_return_val_if_fail (GOA_IS_CLIENT (client), FALSE);
772 g_return_val_if_fail (GOA_IS_OBJECT (object), FALSE);
773 g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), FALSE);
774 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
775
776 ret = FALSE;
777
778 dialog = gtk_dialog_new_with_buttons (NULL,
779 parent,
780 GTK_DIALOG_MODAL
781 | GTK_DIALOG_DESTROY_WITH_PARENT
782 | GTK_DIALOG_USE_HEADER_BAR,
783 NULL,
784 NULL);
785 gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
786 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
787 gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
788
789 vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
790 gtk_box_set_spacing (GTK_BOX (vbox), 12);
791
792 memset (&data, 0, sizeof (AddAccountData));
793 data.cancellable = g_cancellable_new ();
794 data.loop = g_main_loop_new (NULL, FALSE);
795 data.dialog = GTK_DIALOG (dialog);
796 data.error = NULL;
797
798 create_account_details_ui (provider, GTK_DIALOG (dialog), GTK_BOX (vbox), FALSE, &data);
799
800 account = goa_object_peek_account (object);
801 username = goa_account_get_identity (account);
802 gtk_entry_set_text (GTK_ENTRY (data.username), username);
803 gtk_editable_set_editable (GTK_EDITABLE (data.username), FALSE);
804
805 gtk_widget_show_all (dialog);
806 g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data);
807
808 login_again:
809 response = gtk_dialog_run (GTK_DIALOG (dialog));
810 if (response != GTK_RESPONSE_OK)
811 {
812 g_set_error (&data.error, GOA_ERROR, GOA_ERROR_DIALOG_DISMISSED, _("Dialog was dismissed"));
813 goto out;
814 }
815
816 password = gtk_entry_get_text (GTK_ENTRY (data.password));
817 g_cancellable_reset (data.cancellable);
818 lastfm_login (provider,
819 username,
820 password,
821 data.cancellable,
822 (RestProxyCallAsyncCallback) check_cb,
823 &data);
824 gtk_widget_set_sensitive (data.connect_button, FALSE);
825 gtk_widget_show (data.progress_grid);
826 g_main_loop_run (data.loop);
827
828 if (g_cancellable_is_cancelled (data.cancellable))
829 {
830 g_prefix_error (&data.error,
831 _("Dialog was dismissed (%s, %d): "),
832 g_quark_to_string (data.error->domain),
833 data.error->code);
834 data.error->domain = GOA_ERROR;
835 data.error->code = GOA_ERROR_DIALOG_DISMISSED;
836 goto out;
837 }
838 else if (data.error != NULL)
839 {
840 gchar *markup;
841
842 markup = g_strdup_printf ("<b>%s:</b>\n%s", _("Error connecting to Last.fm"), data.error->message);
843 g_clear_error (&data.error);
844
845 gtk_label_set_markup (GTK_LABEL (data.cluebar_label), markup);
846 g_free (markup);
847
848 gtk_button_set_label (GTK_BUTTON (data.connect_button), _("_Try Again"));
849 gtk_widget_set_no_show_all (data.cluebar, FALSE);
850 gtk_widget_show_all (data.cluebar);
851 goto login_again;
852 }
853
854 /* TODO: run in worker thread */
855 g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
856 g_variant_builder_add (&builder, "{sv}", "password", g_variant_new_string (password));
857 g_variant_builder_add (&builder, "{sv}", "access_token", g_variant_new_string (data.access_token));
858
859 if (!goa_utils_store_credentials_for_object_sync (provider,
860 object,
861 g_variant_builder_end (&builder),
862 NULL, /* GCancellable */
863 &data.error))
864 goto out;
865
866 goa_account_call_ensure_credentials (account,
867 NULL, /* GCancellable */
868 NULL, NULL); /* callback, user_data */
869
870 ret = TRUE;
871 out:
872 if (data.error != NULL)
873 g_propagate_error (error, data.error);
874
875 gtk_widget_destroy (dialog);
876 g_clear_pointer (&data.loop, g_main_loop_unref);
877 g_free (data.access_token);
878 g_clear_object (&data.cancellable);
879 return ret;
880 }
881
882 /* ---------------------------------------------------------------------------------------------------- */
883
884 static void
goa_lastfm_provider_init(GoaLastfmProvider * provider)885 goa_lastfm_provider_init (GoaLastfmProvider *provider)
886 {
887 }
888
889 static void
goa_lastfm_provider_class_init(GoaLastfmProviderClass * klass)890 goa_lastfm_provider_class_init (GoaLastfmProviderClass *klass)
891 {
892 GoaProviderClass *provider_class;
893 GoaOAuth2ProviderClass *oauth2_class = GOA_OAUTH2_PROVIDER_CLASS (klass);
894
895 provider_class = GOA_PROVIDER_CLASS (klass);
896 provider_class->get_provider_type = get_provider_type;
897 provider_class->get_provider_name = get_provider_name;
898 provider_class->get_provider_group = get_provider_group;
899 provider_class->get_provider_features = get_provider_features;
900 provider_class->add_account = add_account;
901 provider_class->refresh_account = refresh_account;
902 provider_class->build_object = build_object;
903 provider_class->ensure_credentials_sync = ensure_credentials_sync;
904
905 oauth2_class->get_client_id = get_client_id;
906 oauth2_class->get_client_secret = get_client_secret;
907 }
908