Merge lp:~jamesh/account-polld/accounts-integration into lp:~phablet-team/account-polld/trunk

Proposed by James Henstridge
Status: Merged
Approved by: Sergio Schvezov
Approved revision: 13
Merged at revision: 5
Proposed branch: lp:~jamesh/account-polld/accounts-integration
Merge into: lp:~phablet-team/account-polld/trunk
Prerequisite: lp:~sergiusens/account-polld/daemon
Diff against target: 606 lines (+372/-164)
5 files modified
accounts/account-watcher.c (+279/-0)
accounts/account-watcher.h (+38/-0)
accounts/accounts.c (+16/-152)
accounts/accounts.go (+24/-12)
cmd/account-watcher-test/main.go (+15/-0)
To merge this branch: bzr merge lp:~jamesh/account-polld/accounts-integration
Reviewer Review Type Date Requested Status
Sergio Schvezov Approve
Review via email: mp+226463@code.launchpad.net

Description of the change

Update and refactor the online-accounts binding to support tracking multiple services and multiple instances of each service. I also split the main C logic into a separate file independent of the Go binding since it allowed me to run it independently.

There is a compiler warning due to the callback function exported from Go not matching the signature expected by the C interface: the first argument should be *C.AccountWatcher, but that doesn't seem to work right. The Go 1.3 release notes seem to indicate that they've fixed up handling of incomplete types there, but I haven't tested it yet.

The Go interface remains compatible, but perhaps it could do with some changes too.

To post a comment you must log in.
13. By James Henstridge

Remove unneeded includes.

Revision history for this message
Sergio Schvezov (sergiusens) wrote :

looks good to me, the only thing I'd forward fix for Poll is to use this
type AuthData struct

instead of that map type which seems to just be a complication.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'accounts/account-watcher.c'
2--- accounts/account-watcher.c 1970-01-01 00:00:00 +0000
3+++ accounts/account-watcher.c 2014-07-11 13:44:43 +0000
4@@ -0,0 +1,279 @@
5+/*
6+ Copyright 2014 Canonical Ltd.
7+ Authors: James Henstridge <james.henstridge@canonical.com>
8+
9+ This program is free software: you can redistribute it and/or modify it
10+ under the terms of the GNU General Public License version 3, as published
11+ by the Free Software Foundation.
12+
13+ This program is distributed in the hope that it will be useful, but
14+ WITHOUT ANY WARRANTY; without even the implied warranties of
15+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16+ PURPOSE. See the GNU General Public License for more details.
17+
18+ You should have received a copy of the GNU General Public License along
19+ with this program. If not, see <http://www.gnu.org/licenses/>.
20+*/
21+#include <stdio.h>
22+
23+#include <glib.h>
24+#include <libaccounts-glib/accounts-glib.h>
25+#include <libsignon-glib/signon-glib.h>
26+
27+#include "account-watcher.h"
28+
29+struct _AccountWatcher {
30+ AgManager *manager;
31+ /* A hash table of the service names we are interested in */
32+ GHashTable *services_to_watch;
33+ /* A hash table of the enabled accounts we know of.
34+ * Keys are account ID integers, and AccountInfo structs as values.
35+ */
36+ GHashTable *services;
37+
38+ gulong enabled_event_signal_id;
39+ gulong account_deleted_signal_id;
40+
41+ AccountEnabledCallback callback;
42+ void *user_data;
43+};
44+
45+typedef struct _AccountInfo AccountInfo;
46+struct _AccountInfo {
47+ AccountWatcher *watcher;
48+ /* Manage signin session for account */
49+ AgAccountService *account_service;
50+ SignonAuthSession *session;
51+ GVariant *auth_params;
52+ GVariant *session_data;
53+
54+ gulong enabled_signal_id;
55+ AgAccountId account_id;
56+ gboolean enabled; /* the last known state of the account */
57+};
58+
59+static void account_info_clear_login(AccountInfo *info) {
60+ if (info->session_data) {
61+ g_variant_unref(info->session_data);
62+ info->session_data = NULL;
63+ }
64+ if (info->auth_params) {
65+ g_variant_unref(info->auth_params);
66+ info->auth_params = NULL;
67+ }
68+ if (info->session) {
69+ signon_auth_session_cancel(info->session);
70+ g_object_unref(info->session);
71+ info->session = NULL;
72+ }
73+}
74+
75+static void account_info_free(AccountInfo *info) {
76+ account_info_clear_login(info);
77+ if (info->enabled_signal_id != 0) {
78+ g_signal_handler_disconnect(
79+ info->account_service, info->enabled_signal_id);
80+ }
81+ info->enabled_signal_id = 0;
82+ if (info->account_service) {
83+ g_object_unref(info->account_service);
84+ info->account_service = NULL;
85+ }
86+ g_free(info);
87+}
88+
89+static void account_info_notify(AccountInfo *info) {
90+ AgService *service = ag_account_service_get_service(info->account_service);
91+ const char *service_name = ag_service_get_name(service);
92+ char *client_id = NULL;
93+ char *client_secret = NULL;
94+ char *access_token = NULL;
95+ char *token_secret = NULL;
96+
97+ if (info->enabled) {
98+ /* Look up OAuth 2 parameters, falling back to OAuth 1 names */
99+ g_variant_lookup(info->auth_params, "ClientId", "&s", &client_id);
100+ g_variant_lookup(info->auth_params, "ClientSecret", "&s", &client_secret);
101+ g_variant_lookup(info->session_data, "AccessToken", "&s", &access_token);
102+ g_variant_lookup(info->session_data, "TokenSecret", "&s", &token_secret);
103+
104+ if (client_id == NULL) {
105+ g_variant_lookup(info->auth_params, "ConsumerKey", "&s", &client_id);
106+ }
107+ if (client_secret == NULL) {
108+ g_variant_lookup(info->auth_params, "ConsumerSecret", "&s", &client_secret);
109+ }
110+ }
111+
112+ info->watcher->callback(info->watcher,
113+ info->account_id,
114+ service_name,
115+ info->enabled,
116+ client_id,
117+ client_secret,
118+ access_token,
119+ token_secret,
120+ info->watcher->user_data);
121+}
122+
123+static void account_info_login_cb(GObject *source, GAsyncResult *result, void *user_data) {
124+ SignonAuthSession *session = (SignonAuthSession *)source;
125+ AccountInfo *info = (AccountInfo *)user_data;
126+
127+ fprintf(stderr, "Authentication for account %u complete\n", info->account_id);
128+
129+ GError *error = NULL;
130+ info->session_data = signon_auth_session_process_finish(session, result, &error);
131+
132+ if (error != NULL) {
133+ fprintf(stderr, "Authentication failed: %s\n", error->message);
134+ g_error_free(error);
135+ return;
136+ }
137+ account_info_notify(info);
138+}
139+
140+static void account_info_enabled_cb(
141+ AgAccountService *account_service, gboolean enabled, AccountInfo *info) {
142+ fprintf(stderr, "account_info_enabled_cb for %u, enabled=%d\n", info->account_id, enabled);
143+ if (info->enabled == enabled) {
144+ /* no change */
145+ return;
146+ }
147+ info->enabled = enabled;
148+
149+ account_info_clear_login(info);
150+ if (!enabled) {
151+ // Send notification that account has been disabled */
152+ account_info_notify(info);
153+ return;
154+ }
155+
156+ AgAuthData *auth_data = ag_account_service_get_auth_data(account_service);
157+ GError *error = NULL;
158+ fprintf(stderr, "Starting authentication session for account %u\n", info->account_id);
159+ info->session = signon_auth_session_new(
160+ ag_auth_data_get_credentials_id(auth_data),
161+ ag_auth_data_get_method(auth_data), &error);
162+ if (error != NULL) {
163+ fprintf(stderr, "Could not set up auth session: %s\n", error->message);
164+ g_error_free(error);
165+ g_object_unref(auth_data);
166+ return;
167+ }
168+
169+ GVariantBuilder builder;
170+ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
171+ g_variant_builder_add(
172+ &builder, "{sv}",
173+ SIGNON_SESSION_DATA_UI_POLICY,
174+ g_variant_new_int32(SIGNON_POLICY_NO_USER_INTERACTION));
175+
176+ info->auth_params = g_variant_ref_sink(
177+ ag_auth_data_get_login_parameters(
178+ auth_data, g_variant_builder_end(&builder)));
179+
180+ signon_auth_session_process_async(
181+ info->session,
182+ info->auth_params,
183+ ag_auth_data_get_mechanism(auth_data),
184+ NULL, /* cancellable */
185+ account_info_login_cb, info);
186+ ag_auth_data_unref(auth_data);
187+}
188+
189+static AccountInfo *account_info_new(AccountWatcher *watcher, AgAccountService *account_service) {
190+ AccountInfo *info = g_new0(AccountInfo, 1);
191+ info->watcher = watcher;
192+ info->account_service = g_object_ref(account_service);
193+
194+ AgAccount *account = ag_account_service_get_account(account_service);
195+ g_object_get(account, "id", &info->account_id, NULL);
196+
197+ info->enabled_signal_id = g_signal_connect(
198+ account_service, "enabled",
199+ G_CALLBACK(account_info_enabled_cb), info);
200+ // Set initial state
201+ account_info_enabled_cb(account_service, ag_account_service_get_enabled(account_service), info);
202+
203+ return info;
204+}
205+
206+static void account_watcher_enabled_event_cb(
207+ AgManager *manager, AgAccountId account_id, AccountWatcher *watcher) {
208+ fprintf(stderr, "enabled-event for %u\n", account_id);
209+ if (g_hash_table_contains(watcher->services, GUINT_TO_POINTER(account_id))) {
210+ /* We are already tracking this account */
211+ return;
212+ }
213+ AgAccount *account = ag_manager_get_account(manager, account_id);
214+ if (account == NULL) {
215+ /* There was a problem looking up the account */
216+ return;
217+ }
218+ GList *services = ag_account_list_services(account);
219+ GList *l;
220+ for (l = services; l != NULL; l = l->next) {
221+ AgService *service = l->data;
222+
223+ const char *name = ag_service_get_name(service);
224+ if (g_hash_table_contains(watcher->services_to_watch, name)) {
225+ AgAccountService *account_service = ag_account_service_new(
226+ account, service);
227+ AccountInfo *info = account_info_new(watcher, account_service);
228+ g_object_unref(account_service);
229+ g_hash_table_insert(watcher->services, GUINT_TO_POINTER(account_id), info);
230+ break;
231+ }
232+ }
233+ ag_service_list_free(services);
234+ g_object_unref(account);
235+}
236+
237+static void account_watcher_account_deleted_cb(
238+ AgManager *manager, AgAccountId account_id, AccountWatcher *watcher) {
239+ fprintf(stderr, "account-deleted for %u\n", account_id);
240+ /* A disabled event should have been sent prior to this, so no
241+ * need to send any notification. */
242+ g_hash_table_remove(watcher->services, GUINT_TO_POINTER(account_id));
243+}
244+
245+static gboolean account_watcher_setup(void *user_data) {
246+ AccountWatcher *watcher = (AccountWatcher *)user_data;
247+
248+ /* Track changes to accounts */
249+ watcher->enabled_event_signal_id = g_signal_connect(
250+ watcher->manager, "enabled-event",
251+ G_CALLBACK(account_watcher_enabled_event_cb), watcher);
252+ watcher->account_deleted_signal_id = g_signal_connect(
253+ watcher->manager, "account-deleted",
254+ G_CALLBACK(account_watcher_account_deleted_cb), watcher);
255+
256+ /* Now check initial state */
257+ GList *enabled_accounts = ag_manager_list(watcher->manager);
258+ GList *l;
259+ for (l = enabled_accounts; l != NULL; l = l->next) {
260+ AgAccountId account_id = GPOINTER_TO_UINT(l->data);
261+ account_watcher_enabled_event_cb(watcher->manager, account_id, watcher);
262+ }
263+ ag_manager_list_free(enabled_accounts);
264+
265+ return FALSE;
266+}
267+
268+AccountWatcher *account_watcher_new(GHashTable *services_to_watch,
269+ AccountEnabledCallback callback,
270+ void *user_data) {
271+ AccountWatcher *watcher = g_new0(AccountWatcher, 1);
272+
273+ watcher->manager = ag_manager_new();
274+ watcher->services_to_watch = g_hash_table_ref(services_to_watch);
275+ watcher->services = g_hash_table_new_full(
276+ g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)account_info_free);
277+ watcher->callback = callback;
278+ watcher->user_data = user_data;
279+
280+ /* Make sure main setup occurs within the mainloop thread */
281+ g_idle_add(account_watcher_setup, watcher);
282+ return watcher;
283+}
284
285=== added file 'accounts/account-watcher.h'
286--- accounts/account-watcher.h 1970-01-01 00:00:00 +0000
287+++ accounts/account-watcher.h 2014-07-11 13:44:43 +0000
288@@ -0,0 +1,38 @@
289+/*
290+ Copyright 2014 Canonical Ltd.
291+ Authors: James Henstridge <james.henstridge@canonical.com>
292+
293+ This program is free software: you can redistribute it and/or modify it
294+ under the terms of the GNU General Public License version 3, as published
295+ by the Free Software Foundation.
296+
297+ This program is distributed in the hope that it will be useful, but
298+ WITHOUT ANY WARRANTY; without even the implied warranties of
299+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
300+ PURPOSE. See the GNU General Public License for more details.
301+
302+ You should have received a copy of the GNU General Public License along
303+ with this program. If not, see <http://www.gnu.org/licenses/>.
304+*/
305+#ifndef ACCOUNT_WATCHER_H
306+#define ACCOUNT_WATCHER_H
307+
308+#include <glib.h>
309+
310+typedef struct _AccountWatcher AccountWatcher;
311+
312+typedef void (*AccountEnabledCallback)(AccountWatcher *watcher,
313+ unsigned int account_id,
314+ const char *service_name,
315+ int enabled,
316+ const char *client_id,
317+ const char *client_secret,
318+ const char *access_token,
319+ const char *token_secret,
320+ void *user_data);
321+
322+AccountWatcher *account_watcher_new(GHashTable *services_to_watch,
323+ AccountEnabledCallback callback,
324+ void *user_data);
325+
326+#endif
327
328=== modified file 'accounts/accounts.c'
329--- accounts/accounts.c 2014-07-11 13:44:43 +0000
330+++ accounts/accounts.c 2014-07-11 13:44:43 +0000
331@@ -1,154 +1,18 @@
332 #include "_cgo_export.h"
333-#include <stdio.h>
334-#include <string.h>
335-#include <unistd.h>
336-
337-#include <glib.h>
338-#include <libaccounts-glib/accounts-glib.h>
339-#include <libsignon-glib/signon-glib.h>
340-
341-typedef struct _AuthContext AuthContext;
342-struct _AuthContext {
343- AgManager *manager;
344- char *service_name;
345- AgAccountService *account_service;
346- AgAuthData *auth_data;
347- SignonAuthSession *session;
348-
349- GVariant *auth_params;
350- GVariant *session_data;
351-};
352-
353-static void login_cb(GObject *source, GAsyncResult *result, void *user_data) {
354- SignonAuthSession *session = (SignonAuthSession *)source;
355- AuthContext *ctx = (AuthContext *)user_data;
356-
357- GError *error = NULL;
358- ctx->session_data = signon_auth_session_process_finish(session, result, &error);
359-
360- g_object_unref(ctx->session);
361- ctx->session = NULL;
362-
363- if (error != NULL) {
364- fprintf(stderr, "Authentication failed: %s\n", error->message);
365- g_error_free(error);
366- return;
367- }
368-
369- char *client_id = NULL;
370- char *client_secret = NULL;
371- char *access_token = NULL;
372- g_variant_lookup(ctx->auth_params, "ClientId", "&s", &client_id);
373- g_variant_lookup(ctx->auth_params, "ClientSecret", "&s", &client_secret);
374- g_variant_lookup(ctx->session_data, "AccessToken", "&s", &access_token);
375-
376- authLogin(ctx, client_id, client_secret, access_token);
377-}
378-
379-static void login_service(AuthContext *ctx) {
380- ctx->auth_data = ag_account_service_get_auth_data(ctx->account_service);
381-
382- GError *error = NULL;
383- ctx->session = signon_auth_session_new(
384- ag_auth_data_get_credentials_id(ctx->auth_data),
385- ag_auth_data_get_method(ctx->auth_data), &error);
386- if (error != NULL) {
387- fprintf(stderr, "Could not set up auth session: %s\n", error->message);
388- g_error_free(error);
389- return;
390- }
391-
392- GVariantBuilder builder;
393- g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
394- g_variant_builder_add(
395- &builder, "{sv}",
396- SIGNON_SESSION_DATA_UI_POLICY,
397- g_variant_new_int32(SIGNON_POLICY_NO_USER_INTERACTION));
398-
399- ctx->auth_params = g_variant_ref_sink(
400- ag_auth_data_get_login_parameters(
401- ctx->auth_data, g_variant_builder_end(&builder)));
402-
403- signon_auth_session_process_async(
404- ctx->session,
405- ctx->auth_params,
406- ag_auth_data_get_mechanism(ctx->auth_data),
407- NULL, /* cancellable */
408- login_cb, ctx);
409-}
410-
411-static void logout_service(AuthContext *ctx) {
412- if (ctx->session_data) {
413- g_variant_unref(ctx->session_data);
414- ctx->session_data = NULL;
415- }
416- if (ctx->auth_params) {
417- g_variant_unref(ctx->auth_params);
418- ctx->auth_params = NULL;
419- }
420- if (ctx->session) {
421- signon_auth_session_cancel(ctx->session);
422- g_object_unref(ctx->session);
423- ctx->session = NULL;
424- }
425- if (ctx->auth_data) {
426- g_object_unref(ctx->auth_data);
427- ctx->auth_data = NULL;
428- }
429- if (ctx->account_service) {
430- g_object_unref(ctx->account_service);
431- ctx->account_service = NULL;
432- }
433-
434- authLogin(ctx, NULL, NULL, NULL);
435-}
436-
437-static void lookup_account_service(AuthContext *ctx) {
438- GList *account_services = ag_manager_get_enabled_account_services(ctx->manager);
439- GList *tmp;
440- for (tmp = account_services; tmp != NULL; tmp = tmp->next) {
441- AgAccountService *acct_svc = AG_ACCOUNT_SERVICE(tmp->data);
442- AgService *service = ag_account_service_get_service(acct_svc);
443- if (!strcmp(ctx->service_name, ag_service_get_name(service))) {
444- ctx->account_service = g_object_ref(acct_svc);
445- break;
446- }
447- }
448- g_list_foreach(account_services, (GFunc)g_object_unref, NULL);
449- g_list_free(account_services);
450-
451- if (ctx->account_service != NULL) {
452- login_service(ctx);
453- }
454-}
455-
456-static void account_enabled_cb(AgManager *manager, guint account_id, void *user_data) {
457- AuthContext *ctx = (AuthContext *)user_data;
458-
459- printf("enabled_cb account_id=%u\n", account_id);
460-
461- if (ctx->account_service != NULL &&
462- !ag_account_service_get_enabled(ctx->account_service)) {
463- logout_service(ctx);
464- }
465- lookup_account_service(ctx);
466-}
467-
468-static gboolean
469-setup_context(void *user_data) {
470- AuthContext *ctx = (AuthContext *)user_data;
471-
472- lookup_account_service(ctx);
473- g_signal_connect(ctx->manager, "enabled-event",
474- G_CALLBACK(account_enabled_cb), ctx);
475- return FALSE;
476-}
477-
478-AuthContext *watch_for_service(const char *service_name) {
479- AuthContext *ctx = g_new0(AuthContext, 1);
480- ctx->manager = ag_manager_new();
481- ctx->service_name = g_strdup(service_name);
482-
483- g_idle_add(setup_context, ctx);
484- return ctx;
485+#include "account-watcher.h"
486+
487+AccountWatcher *watch_for_services(void *array_of_service_names, int length) {
488+ /* Transfer service names to hash table */
489+ GoString *service_names = (GoString *)array_of_service_names;
490+ GHashTable *services_to_watch = g_hash_table_new_full(
491+ g_str_hash, g_str_equal, g_free, NULL);
492+ int i;
493+ for (i = 0; i < length; i++) {
494+ g_hash_table_insert(services_to_watch, g_strdup(service_names[i].p), NULL);
495+ }
496+
497+ AccountWatcher *watcher = account_watcher_new(
498+ services_to_watch, authCallback, NULL);
499+ g_hash_table_unref(services_to_watch);
500+ return watcher;
501 }
502
503=== modified file 'accounts/accounts.go'
504--- accounts/accounts.go 2014-07-11 13:44:43 +0000
505+++ accounts/accounts.go 2014-07-11 13:44:43 +0000
506@@ -22,49 +22,58 @@
507 #include <stdlib.h>
508 #include <glib.h>
509
510-typedef struct _AuthContext AuthContext;
511+typedef struct _AccountWatcher AccountWatcher;
512
513-AuthContext *watch_for_service(const char *service_name);
514+AccountWatcher *watch_for_services(void *array_of_service_names, int length);
515 */
516 import "C"
517 import "unsafe"
518
519 type AuthData struct {
520+ AccountId uint
521+ ServiceName string
522+ Enabled bool
523+
524 ClientId string
525 ClientSecret string
526 AccessToken string
527+ TokenSecret string
528 }
529
530 var (
531 mainLoop *C.GMainLoop
532- authChannels = make(map[*C.AuthContext]chan<- AuthData)
533+ authChannels = make(map[*C.AccountWatcher]chan<- AuthData)
534 )
535
536-func WatchForService(serviceName string) <-chan AuthData {
537+func WatchForService(serviceNames... string) <-chan AuthData {
538 if mainLoop == nil {
539 mainLoop = C.g_main_loop_new(nil, C.gboolean(1))
540 go C.g_main_loop_run(mainLoop)
541 }
542
543- cService := C.CString(serviceName)
544- defer C.free(unsafe.Pointer(cService))
545- ctx := C.watch_for_service(cService)
546+ watcher := C.watch_for_services(unsafe.Pointer(&serviceNames[0]), C.int(len(serviceNames)))
547
548 ch := make(chan AuthData)
549- authChannels[ctx] = ch
550+ authChannels[watcher] = ch
551 return ch
552 }
553
554-//export authLogin
555-func authLogin(user_data unsafe.Pointer, clientId *C.char, clientSecret *C.char, accessToken *C.char) {
556- ctx := (*C.AuthContext)(user_data)
557- ch := authChannels[ctx]
558+//export authCallback
559+func authCallback(watcher unsafe.Pointer, accountId C.uint, serviceName *C.char, enabled C.int, clientId, clientSecret, accessToken, tokenSecret *C.char, userData unsafe.Pointer) {
560+ // Ideally the first argument would be of type
561+ // *C.AccountWatcher, but that fails with Go 1.2.
562+ ch := authChannels[(*C.AccountWatcher)(watcher)]
563 if ch == nil {
564 // Log the error
565 return
566 }
567
568 var data AuthData
569+ data.AccountId = uint(accountId)
570+ data.ServiceName = C.GoString(serviceName)
571+ if enabled != 0 {
572+ data.Enabled = true
573+ }
574 if clientId != nil {
575 data.ClientId = C.GoString(clientId)
576 }
577@@ -74,5 +83,8 @@
578 if accessToken != nil {
579 data.AccessToken = C.GoString(accessToken)
580 }
581+ if tokenSecret != nil {
582+ data.TokenSecret = C.GoString(tokenSecret)
583+ }
584 ch <- data
585 }
586
587=== added directory 'cmd/account-watcher-test'
588=== added file 'cmd/account-watcher-test/main.go'
589--- cmd/account-watcher-test/main.go 1970-01-01 00:00:00 +0000
590+++ cmd/account-watcher-test/main.go 2014-07-11 13:44:43 +0000
591@@ -0,0 +1,15 @@
592+package main
593+
594+import (
595+ "fmt"
596+ "os"
597+
598+ "launchpad.net/account-polld/accounts"
599+)
600+
601+func main() {
602+ // Expects a list of service names as command line arguments
603+ for data := range accounts.WatchForService(os.Args[1:]...) {
604+ fmt.Printf("%#v\n", data)
605+ }
606+}

Subscribers

People subscribed via source and target branches