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

Proposed by James Henstridge
Status: Merged
Approved by: Sergio Schvezov
Approved revision: 30
Merged at revision: 21
Proposed branch: lp:~jamesh/account-polld/account-refresh
Merge into: lp:~phablet-team/account-polld/trunk
Diff against target: 868 lines (+306/-123)
18 files modified
accounts/account-watcher.c (+80/-55)
accounts/account-watcher.h (+4/-1)
accounts/accounts.c (+16/-10)
accounts/accounts.go (+47/-14)
cmd/account-polld/account_manager.go (+30/-18)
cmd/account-polld/main.go (+8/-4)
cmd/account-watcher-test/main.go (+6/-2)
data/account-polld.application (+5/-11)
data/account-polld.service-type (+7/-0)
data/facebook-poll.service (+8/-0)
data/google-gmail-poll.service (+1/-1)
data/twitter-poll.service (+8/-0)
debian/rules (+4/-1)
plugins/facebook/facebook.go (+3/-0)
plugins/facebook/facebook_test.go (+25/-5)
plugins/plugins.go (+9/-1)
plugins/twitter/twitter.go (+12/-0)
plugins/twitter/twitter_test.go (+33/-0)
To merge this branch: bzr merge lp:~jamesh/account-polld/account-refresh
Reviewer Review Type Date Requested Status
Sergio Schvezov Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+227759@code.launchpad.net

Commit message

Add support for handling token expiry.

Description of the change

Add support for handling token expiry. The basic mechanism is that:

1. plugins convert their "expired token" errors into the plugins.ErrTokenExpired error.
2. the AccountManager handles this error by calling watcher.Refresh(accountId)
3. the new token is returned via the watcher's channel, as with other changes.

Along the way, I made a few changes:

* the accounts package now exposes a Watcher object holding the channel. This was so I'd have somewhere to hang the Refresh() method.
* I replaced the AccountManager's terminate channel with a channel for receiving the AuthData. Closing this channel is used as a terminate signal, and removed the need for a mutex.

To post a comment you must log in.
Revision history for this message
Sergio Schvezov (sergiusens) wrote :

Looks good, just added one minor inline comment.

Another non related to this change comment is to ask if it would be ok to change the fprintf's to call go's log.Print.*

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

ideally we should only print from package main, so this is really optional.

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

I don't think it's catching accounts being created while running, so if I create my twitter account during a polld run it never shows up (I do go into the account and enable/disable the toggle)

if I restart polld the, the account is seen. Looking at the code again, I see only one call to ag_manager_list and no callback setup for new accounts. Should that be fixed in this merge?

Rest of the code looks fine.

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

Looking again, it should be handled by account_watcher_enabled_event_cb; not sure why I'm not seeing new accounts then... (I was playing with twitter fwiw).

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
23. By James Henstridge

Convert account watcher to track a single service type rather than a set
of service names. This is needed to properly receive enabled event
notifications.

24. By James Henstridge

Add service files for Facebook and Twitter, and a service type file that
lets us select them along with GMail.

25. By James Henstridge

Fix up a crash when we are reporting errors to Go and no session data is
available.

26. By James Henstridge

Switch to the new prototype for accounts.NewWatcher() and the new
service names for Twitter and Facebook.

27. By James Henstridge

Disable the debug spew from account-watcher.c

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
James Henstridge (jamesh) wrote :

I was able to confirm the problem detecting new accounts, and believe I've fixed that in this branch.

It looks like the enabled-event isn't reliably sent out unless the account manager is limited to a single service type. So I've added an account-polld.service-type file along with services for Facebook and Twitter (as discussed during the call on Tuesday). The AccountWatcher now watches for all account services matching "account-polld", so gets notified of the three services we're interested in. This seems to do the trick and simplifies things a bit.

I've also disabled the debug spew from account-watcher.c, while still allowing it to be turned back on if I need to track down some logic bugs.

28. By James Henstridge

Merge from trunk

29. By James Henstridge

Install additional online accounts files.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
30. By James Henstridge

Fix typo.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Sergio Schvezov (sergiusens) wrote :

Using service types doesn't seem to work with gmail

Revision history for this message
James Henstridge (jamesh) wrote :

Can you be more specific about what doesn't work? Are you using the updated .service file? (note that I updated the service type here).

When I run "./account-watcher-test account-polld" with the extra files installed, I get auth data for all three of my accounts.

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

I never get the enabled signal for gmail, nor do I see the application toggle in online accounts when testing on the phone (haven't tried on the desktop).

There's also a typo in debian/rules I've made
669 ${CURDIR}/debian/account-polld/usr/share/accounts/application
and the cp is missing a trailing 's'.

But my testing involved installing
http://jenkins.qa.ubuntu.com/job/account-polld-utopic-armhf-ci/6/artifact/work/output/*zip*/output.zip and moving the .application afterwards

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

what I am seeing are most likely online accounts issues which I can reproduce with sync-monitor as well.

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

I'll get this in a silo in the morning; feel free to do so yourself if you want; I'll chain up some MPs similar to this one
https://code.launchpad.net/~sergiusens/account-polld/oa_fixes/+merge/228195

if you don't mind doing them here as well (at least the trailing 's')

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'accounts/account-watcher.c'
--- accounts/account-watcher.c 2014-07-11 12:01:42 +0000
+++ accounts/account-watcher.c 2014-07-24 08:50:37 +0000
@@ -22,10 +22,15 @@
2222
23#include "account-watcher.h"23#include "account-watcher.h"
2424
25/* #define DEBUG */
26#ifdef DEBUG
27# define trace(...) fprintf(stderr, __VA_ARGS__)
28#else
29# define trace(...)
30#endif
31
25struct _AccountWatcher {32struct _AccountWatcher {
26 AgManager *manager;33 AgManager *manager;
27 /* A hash table of the service names we are interested in */
28 GHashTable *services_to_watch;
29 /* A hash table of the enabled accounts we know of.34 /* A hash table of the enabled accounts we know of.
30 * Keys are account ID integers, and AccountInfo structs as values.35 * Keys are account ID integers, and AccountInfo structs as values.
31 */36 */
@@ -82,7 +87,7 @@
82 g_free(info);87 g_free(info);
83}88}
8489
85static void account_info_notify(AccountInfo *info) {90static void account_info_notify(AccountInfo *info, GError *error) {
86 AgService *service = ag_account_service_get_service(info->account_service);91 AgService *service = ag_account_service_get_service(info->account_service);
87 const char *service_name = ag_service_get_name(service);92 const char *service_name = ag_service_get_name(service);
88 char *client_id = NULL;93 char *client_id = NULL;
@@ -90,13 +95,10 @@
90 char *access_token = NULL;95 char *access_token = NULL;
91 char *token_secret = NULL;96 char *token_secret = NULL;
9297
93 if (info->enabled) {98 if (info->auth_params != NULL) {
94 /* Look up OAuth 2 parameters, falling back to OAuth 1 names */99 /* Look up OAuth 2 parameters, falling back to OAuth 1 names */
95 g_variant_lookup(info->auth_params, "ClientId", "&s", &client_id);100 g_variant_lookup(info->auth_params, "ClientId", "&s", &client_id);
96 g_variant_lookup(info->auth_params, "ClientSecret", "&s", &client_secret);101 g_variant_lookup(info->auth_params, "ClientSecret", "&s", &client_secret);
97 g_variant_lookup(info->session_data, "AccessToken", "&s", &access_token);
98 g_variant_lookup(info->session_data, "TokenSecret", "&s", &token_secret);
99
100 if (client_id == NULL) {102 if (client_id == NULL) {
101 g_variant_lookup(info->auth_params, "ConsumerKey", "&s", &client_id);103 g_variant_lookup(info->auth_params, "ConsumerKey", "&s", &client_id);
102 }104 }
@@ -104,10 +106,15 @@
104 g_variant_lookup(info->auth_params, "ConsumerSecret", "&s", &client_secret);106 g_variant_lookup(info->auth_params, "ConsumerSecret", "&s", &client_secret);
105 }107 }
106 }108 }
109 if (info->session_data != NULL) {
110 g_variant_lookup(info->session_data, "AccessToken", "&s", &access_token);
111 g_variant_lookup(info->session_data, "TokenSecret", "&s", &token_secret);
112 }
107113
108 info->watcher->callback(info->watcher,114 info->watcher->callback(info->watcher,
109 info->account_id,115 info->account_id,
110 service_name,116 service_name,
117 error,
111 info->enabled,118 info->enabled,
112 client_id,119 client_id,
113 client_secret,120 client_secret,
@@ -120,58 +127,38 @@
120 SignonAuthSession *session = (SignonAuthSession *)source;127 SignonAuthSession *session = (SignonAuthSession *)source;
121 AccountInfo *info = (AccountInfo *)user_data;128 AccountInfo *info = (AccountInfo *)user_data;
122129
123 fprintf(stderr, "Authentication for account %u complete\n", info->account_id);130 trace("Authentication for account %u complete\n", info->account_id);
124131
125 GError *error = NULL;132 GError *error = NULL;
126 info->session_data = signon_auth_session_process_finish(session, result, &error);133 info->session_data = signon_auth_session_process_finish(session, result, &error);
134 account_info_notify(info, error);
127135
128 if (error != NULL) {136 if (error != NULL) {
129 fprintf(stderr, "Authentication failed: %s\n", error->message);137 trace("Authentication failed: %s\n", error->message);
130 g_error_free(error);138 g_error_free(error);
131 return;
132 }139 }
133 account_info_notify(info);
134}140}
135141
136static void account_info_enabled_cb(142static void account_info_login(AccountInfo *info) {
137 AgAccountService *account_service, gboolean enabled, AccountInfo *info) {
138 fprintf(stderr, "account_info_enabled_cb for %u, enabled=%d\n", info->account_id, enabled);
139 if (info->enabled == enabled) {
140 /* no change */
141 return;
142 }
143 info->enabled = enabled;
144
145 account_info_clear_login(info);143 account_info_clear_login(info);
146 if (!enabled) {
147 // Send notification that account has been disabled */
148 account_info_notify(info);
149 return;
150 }
151144
152 AgAuthData *auth_data = ag_account_service_get_auth_data(account_service);145 AgAuthData *auth_data = ag_account_service_get_auth_data(info->account_service);
153 GError *error = NULL;146 GError *error = NULL;
154 fprintf(stderr, "Starting authentication session for account %u\n", info->account_id);147 trace("Starting authentication session for account %u\n", info->account_id);
155 info->session = signon_auth_session_new(148 info->session = signon_auth_session_new(
156 ag_auth_data_get_credentials_id(auth_data),149 ag_auth_data_get_credentials_id(auth_data),
157 ag_auth_data_get_method(auth_data), &error);150 ag_auth_data_get_method(auth_data), &error);
158 if (error != NULL) {151 if (error != NULL) {
159 fprintf(stderr, "Could not set up auth session: %s\n", error->message);152 trace("Could not set up auth session: %s\n", error->message);
153 account_info_notify(info, error);
160 g_error_free(error);154 g_error_free(error);
161 g_object_unref(auth_data);155 g_object_unref(auth_data);
162 return;156 return;
163 }157 }
164158
165 GVariantBuilder builder;
166 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
167 g_variant_builder_add(
168 &builder, "{sv}",
169 SIGNON_SESSION_DATA_UI_POLICY,
170 g_variant_new_int32(SIGNON_POLICY_NO_USER_INTERACTION));
171
172 info->auth_params = g_variant_ref_sink(159 info->auth_params = g_variant_ref_sink(
173 ag_auth_data_get_login_parameters(160 ag_auth_data_get_login_parameters(
174 auth_data, g_variant_builder_end(&builder)));161 auth_data, NULL));
175162
176 signon_auth_session_process_async(163 signon_auth_session_process_async(
177 info->session,164 info->session,
@@ -182,6 +169,24 @@
182 ag_auth_data_unref(auth_data);169 ag_auth_data_unref(auth_data);
183}170}
184171
172static void account_info_enabled_cb(
173 AgAccountService *account_service, gboolean enabled, AccountInfo *info) {
174 trace("account_info_enabled_cb for %u, enabled=%d\n", info->account_id, enabled);
175 if (info->enabled == enabled) {
176 /* no change */
177 return;
178 }
179 info->enabled = enabled;
180
181 if (enabled) {
182 account_info_login(info);
183 } else {
184 account_info_clear_login(info);
185 // Send notification that account has been disabled */
186 account_info_notify(info, NULL);
187 }
188}
189
185static AccountInfo *account_info_new(AccountWatcher *watcher, AgAccountService *account_service) {190static AccountInfo *account_info_new(AccountWatcher *watcher, AgAccountService *account_service) {
186 AccountInfo *info = g_new0(AccountInfo, 1);191 AccountInfo *info = g_new0(AccountInfo, 1);
187 info->watcher = watcher;192 info->watcher = watcher;
@@ -201,7 +206,7 @@
201206
202static void account_watcher_enabled_event_cb(207static void account_watcher_enabled_event_cb(
203 AgManager *manager, AgAccountId account_id, AccountWatcher *watcher) {208 AgManager *manager, AgAccountId account_id, AccountWatcher *watcher) {
204 fprintf(stderr, "enabled-event for %u\n", account_id);209 trace("enabled-event for %u\n", account_id);
205 if (g_hash_table_contains(watcher->services, GUINT_TO_POINTER(account_id))) {210 if (g_hash_table_contains(watcher->services, GUINT_TO_POINTER(account_id))) {
206 /* We are already tracking this account */211 /* We are already tracking this account */
207 return;212 return;
@@ -211,20 +216,16 @@
211 /* There was a problem looking up the account */216 /* There was a problem looking up the account */
212 return;217 return;
213 }218 }
219 /* Since our AgManager is restricted to a particular service type,
220 * pick the first service for the account. */
214 GList *services = ag_account_list_services(account);221 GList *services = ag_account_list_services(account);
215 GList *l;222 if (services != NULL) {
216 for (l = services; l != NULL; l = l->next) {223 AgService *service = services->data;
217 AgService *service = l->data;224 AgAccountService *account_service = ag_account_service_new(
218225 account, service);
219 const char *name = ag_service_get_name(service);226 AccountInfo *info = account_info_new(watcher, account_service);
220 if (g_hash_table_contains(watcher->services_to_watch, name)) {227 g_object_unref(account_service);
221 AgAccountService *account_service = ag_account_service_new(228 g_hash_table_insert(watcher->services, GUINT_TO_POINTER(account_id), info);
222 account, service);
223 AccountInfo *info = account_info_new(watcher, account_service);
224 g_object_unref(account_service);
225 g_hash_table_insert(watcher->services, GUINT_TO_POINTER(account_id), info);
226 break;
227 }
228 }229 }
229 ag_service_list_free(services);230 ag_service_list_free(services);
230 g_object_unref(account);231 g_object_unref(account);
@@ -232,7 +233,7 @@
232233
233static void account_watcher_account_deleted_cb(234static void account_watcher_account_deleted_cb(
234 AgManager *manager, AgAccountId account_id, AccountWatcher *watcher) {235 AgManager *manager, AgAccountId account_id, AccountWatcher *watcher) {
235 fprintf(stderr, "account-deleted for %u\n", account_id);236 trace("account-deleted for %u\n", account_id);
236 /* A disabled event should have been sent prior to this, so no237 /* A disabled event should have been sent prior to this, so no
237 * need to send any notification. */238 * need to send any notification. */
238 g_hash_table_remove(watcher->services, GUINT_TO_POINTER(account_id));239 g_hash_table_remove(watcher->services, GUINT_TO_POINTER(account_id));
@@ -258,16 +259,15 @@
258 }259 }
259 ag_manager_list_free(enabled_accounts);260 ag_manager_list_free(enabled_accounts);
260261
261 return FALSE;262 return G_SOURCE_REMOVE;
262}263}
263264
264AccountWatcher *account_watcher_new(GHashTable *services_to_watch,265AccountWatcher *account_watcher_new(const char *service_type,
265 AccountEnabledCallback callback,266 AccountEnabledCallback callback,
266 void *user_data) {267 void *user_data) {
267 AccountWatcher *watcher = g_new0(AccountWatcher, 1);268 AccountWatcher *watcher = g_new0(AccountWatcher, 1);
268269
269 watcher->manager = ag_manager_new();270 watcher->manager = ag_manager_new_for_service_type(service_type);
270 watcher->services_to_watch = g_hash_table_ref(services_to_watch);
271 watcher->services = g_hash_table_new_full(271 watcher->services = g_hash_table_new_full(
272 g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)account_info_free);272 g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)account_info_free);
273 watcher->callback = callback;273 watcher->callback = callback;
@@ -277,3 +277,28 @@
277 g_idle_add(account_watcher_setup, watcher);277 g_idle_add(account_watcher_setup, watcher);
278 return watcher;278 return watcher;
279}279}
280
281struct refresh_info {
282 AccountWatcher *watcher;
283 AgAccountId account_id;
284};
285
286static gboolean account_watcher_refresh_cb(void *user_data) {
287 struct refresh_info *data = (struct refresh_info *)user_data;
288
289 AccountInfo *info = g_hash_table_lookup(
290 data->watcher->services, GUINT_TO_POINTER(data->account_id));
291 if (info != NULL) {
292 account_info_login(info);
293 }
294
295 return G_SOURCE_REMOVE;
296}
297
298void account_watcher_refresh(AccountWatcher *watcher, unsigned int account_id) {
299 struct refresh_info *data = g_new(struct refresh_info, 1);
300 data->watcher = watcher;
301 data->account_id = account_id;
302 g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, account_watcher_refresh_cb,
303 data, g_free);
304}
280305
=== modified file 'accounts/account-watcher.h'
--- accounts/account-watcher.h 2014-07-11 12:01:42 +0000
+++ accounts/account-watcher.h 2014-07-24 08:50:37 +0000
@@ -24,6 +24,7 @@
24typedef void (*AccountEnabledCallback)(AccountWatcher *watcher,24typedef void (*AccountEnabledCallback)(AccountWatcher *watcher,
25 unsigned int account_id,25 unsigned int account_id,
26 const char *service_name,26 const char *service_name,
27 GError *error,
27 int enabled,28 int enabled,
28 const char *client_id,29 const char *client_id,
29 const char *client_secret,30 const char *client_secret,
@@ -31,8 +32,10 @@
31 const char *token_secret,32 const char *token_secret,
32 void *user_data);33 void *user_data);
3334
34AccountWatcher *account_watcher_new(GHashTable *services_to_watch,35AccountWatcher *account_watcher_new(const char *service_type,
35 AccountEnabledCallback callback,36 AccountEnabledCallback callback,
36 void *user_data);37 void *user_data);
3738
39void account_watcher_refresh(AccountWatcher *watcher, unsigned int account_id);
40
38#endif41#endif
3942
=== modified file 'accounts/accounts.c'
--- accounts/accounts.c 2014-07-11 13:44:14 +0000
+++ accounts/accounts.c 2014-07-24 08:50:37 +0000
@@ -1,18 +1,24 @@
1#include "_cgo_export.h"1#include "_cgo_export.h"
2#include "account-watcher.h"
32
4AccountWatcher *watch_for_services(void *array_of_service_names, int length) {3AccountWatcher *watch_for_service_type(const char *service_type) {
5 /* Transfer service names to hash table */4 /* Transfer service names to hash table */
6 GoString *service_names = (GoString *)array_of_service_names;5 if (FALSE) {
7 GHashTable *services_to_watch = g_hash_table_new_full(6 /* The Go callback doesn't quite match the
8 g_str_hash, g_str_equal, g_free, NULL);7 * AccountEnabledCallback function prototype, so we cast the
9 int i;8 * argument in the account_watcher_new() call below.
10 for (i = 0; i < length; i++) {9 *
11 g_hash_table_insert(services_to_watch, g_strdup(service_names[i].p), NULL);10 * This is just a check to see that the function still has the
11 * prototype we expect.
12 */
13 void (*unused)(void *watcher,
14 unsigned int account_id, char *service_name,
15 GError *error, int enabled,
16 char *client_id, char *client_secret,
17 char *access_token, char *token_secret,
18 void *user_data) = authCallback;
12 }19 }
1320
14 AccountWatcher *watcher = account_watcher_new(21 AccountWatcher *watcher = account_watcher_new(
15 services_to_watch, authCallback, NULL);22 service_type, (AccountEnabledCallback)authCallback, NULL);
16 g_hash_table_unref(services_to_watch);
17 return watcher;23 return watcher;
18}24}
1925
=== modified file 'accounts/accounts.go'
--- accounts/accounts.go 2014-07-11 12:14:35 +0000
+++ accounts/accounts.go 2014-07-24 08:50:37 +0000
@@ -21,17 +21,26 @@
21#cgo pkg-config: glib-2.0 libaccounts-glib libsignon-glib21#cgo pkg-config: glib-2.0 libaccounts-glib libsignon-glib
22#include <stdlib.h>22#include <stdlib.h>
23#include <glib.h>23#include <glib.h>
2424#include "account-watcher.h"
25typedef struct _AccountWatcher AccountWatcher;25
2626AccountWatcher *watch_for_service_type(const char *service_type);
27AccountWatcher *watch_for_services(void *array_of_service_names, int length);
28*/27*/
29import "C"28import "C"
30import "unsafe"29import (
30 "errors"
31 "sync"
32 "unsafe"
33)
34
35type Watcher struct {
36 C <-chan AuthData
37 watcher *C.AccountWatcher
38}
3139
32type AuthData struct {40type AuthData struct {
33 AccountId uint41 AccountId uint
34 ServiceName string42 ServiceName string
43 Error error
35 Enabled bool44 Enabled bool
3645
37 ClientId string46 ClientId string
@@ -41,28 +50,49 @@
41}50}
4251
43var (52var (
44 mainLoop *C.GMainLoop53 mainLoopOnce sync.Once
45 authChannels = make(map[*C.AccountWatcher]chan<- AuthData)54 authChannels = make(map[*C.AccountWatcher]chan<- AuthData)
55 authChannelsLock sync.Mutex
46)56)
4757
48func WatchForService(serviceNames... string) <-chan AuthData {58func startMainLoop() {
49 if mainLoop == nil {59 mainLoopOnce.Do(func() {
50 mainLoop = C.g_main_loop_new(nil, C.gboolean(1))60 mainLoop := C.g_main_loop_new(nil, C.gboolean(1))
51 go C.g_main_loop_run(mainLoop)61 go C.g_main_loop_run(mainLoop)
52 }62 })
63}
5364
54 watcher := C.watch_for_services(unsafe.Pointer(&serviceNames[0]), C.int(len(serviceNames)))65// NewWatcher creates a new account watcher for the given service names
66func NewWatcher(serviceType string) *Watcher {
67 w := new(Watcher)
68 cServiceType := C.CString(serviceType)
69 defer C.free(unsafe.Pointer(cServiceType))
70 w.watcher = C.watch_for_service_type(cServiceType)
5571
56 ch := make(chan AuthData)72 ch := make(chan AuthData)
57 authChannels[watcher] = ch73 w.C = ch
58 return ch74 authChannelsLock.Lock()
75 authChannels[w.watcher] = ch
76 authChannelsLock.Unlock()
77
78 startMainLoop()
79
80 return w
81}
82
83// Refresh requests that the token for the given account be refreshed.
84// The new access token will be delivered over the watcher's channel.
85func (w *Watcher) Refresh(accountId uint) {
86 C.account_watcher_refresh(w.watcher, C.uint(accountId))
59}87}
6088
61//export authCallback89//export authCallback
62func authCallback(watcher unsafe.Pointer, accountId C.uint, serviceName *C.char, enabled C.int, clientId, clientSecret, accessToken, tokenSecret *C.char, userData unsafe.Pointer) {90func authCallback(watcher unsafe.Pointer, accountId C.uint, serviceName *C.char, error *C.GError, enabled C.int, clientId, clientSecret, accessToken, tokenSecret *C.char, userData unsafe.Pointer) {
63 // Ideally the first argument would be of type91 // Ideally the first argument would be of type
64 // *C.AccountWatcher, but that fails with Go 1.2.92 // *C.AccountWatcher, but that fails with Go 1.2.
93 authChannelsLock.Lock()
65 ch := authChannels[(*C.AccountWatcher)(watcher)]94 ch := authChannels[(*C.AccountWatcher)(watcher)]
95 authChannelsLock.Unlock()
66 if ch == nil {96 if ch == nil {
67 // Log the error97 // Log the error
68 return98 return
@@ -71,6 +101,9 @@
71 var data AuthData101 var data AuthData
72 data.AccountId = uint(accountId)102 data.AccountId = uint(accountId)
73 data.ServiceName = C.GoString(serviceName)103 data.ServiceName = C.GoString(serviceName)
104 if error != nil {
105 data.Error = errors.New(C.GoString((*C.char)(error.message)))
106 }
74 if enabled != 0 {107 if enabled != 0 {
75 data.Enabled = true108 data.Enabled = true
76 }109 }
77110
=== modified file 'cmd/account-polld/account_manager.go'
--- cmd/account-polld/account_manager.go 2014-07-17 18:35:36 +0000
+++ cmd/account-polld/account_manager.go 2014-07-24 08:50:37 +0000
@@ -19,7 +19,6 @@
1919
20import (20import (
21 "log"21 "log"
22 "sync"
23 "time"22 "time"
2423
25 "launchpad.net/account-polld/accounts"24 "launchpad.net/account-polld/accounts"
@@ -27,59 +26,74 @@
27)26)
2827
29type AccountManager struct {28type AccountManager struct {
29 watcher *accounts.Watcher
30 authData accounts.AuthData30 authData accounts.AuthData
31 authMutex *sync.Mutex
32 plugin plugins.Plugin31 plugin plugins.Plugin
33 interval time.Duration32 interval time.Duration
34 postWatch chan *PostWatch33 postWatch chan *PostWatch
35 terminate chan bool34 authChan chan accounts.AuthData
36}35}
3736
38const DEFAULT_INTERVAL = time.Duration(60 * time.Second)37const DEFAULT_INTERVAL = time.Duration(60 * time.Second)
3938
40func NewAccountManager(authData accounts.AuthData, postWatch chan *PostWatch, plugin plugins.Plugin) *AccountManager {39func NewAccountManager(watcher *accounts.Watcher, postWatch chan *PostWatch, plugin plugins.Plugin) *AccountManager {
41 return &AccountManager{40 return &AccountManager{
41 watcher: watcher,
42 plugin: plugin,42 plugin: plugin,
43 authData: authData,43 interval: DEFAULT_INTERVAL,
44 authMutex: &sync.Mutex{},
45 postWatch: postWatch,44 postWatch: postWatch,
46 interval: DEFAULT_INTERVAL,45 authChan: make(chan accounts.AuthData, 1),
47 terminate: make(chan bool),
48 }46 }
49}47}
5048
51func (a *AccountManager) Delete() {49func (a *AccountManager) Delete() {
52 a.terminate <- true50 close(a.authChan)
53}51}
5452
55func (a *AccountManager) Loop() {53func (a *AccountManager) Loop() {
56 defer close(a.terminate)54 var ok bool
55 if a.authData, ok = <- a.authChan; !ok {
56 return
57 }
57L:58L:
58 for {59 for {
59 log.Println("Polling set to", a.interval, "for", a.authData.AccountId)60 log.Println("Polling set to", a.interval, "for", a.authData.AccountId)
60 select {61 select {
61 case <-time.After(a.interval):62 case <-time.After(a.interval):
62 a.poll()63 a.poll()
63 case <-a.terminate:64 case a.authData, ok = <-a.authChan:
64 break L65 if !ok {
66 break L
67 }
68 a.poll()
65 }69 }
66 }70 }
67 log.Printf("Ending poll loop for account %d", a.authData.AccountId)71 log.Printf("Ending poll loop for account %d", a.authData.AccountId)
68}72}
6973
70func (a *AccountManager) poll() {74func (a *AccountManager) poll() {
71 a.authMutex.Lock()
72 defer a.authMutex.Unlock()
73
74 if !a.authData.Enabled {75 if !a.authData.Enabled {
75 log.Println("Account", a.authData.AccountId, "no longer enabled")76 log.Println("Account", a.authData.AccountId, "no longer enabled")
76 return77 return
77 }78 }
7879
80 if a.authData.Error != nil {
81 log.Println("Account", a.authData.AccountId, "failed to authenticate:", a.authData.Error)
82 return
83 }
84
79 if n, err := a.plugin.Poll(&a.authData); err != nil {85 if n, err := a.plugin.Poll(&a.authData); err != nil {
80 log.Print("Error while polling ", a.authData.AccountId, ": ", err)86 log.Print("Error while polling ", a.authData.AccountId, ": ", err)
81 // penalizing the next poll87 // penalizing the next poll
82 a.interval += DEFAULT_INTERVAL88 a.interval += DEFAULT_INTERVAL
89
90 // If the error indicates that the authentication
91 // token has expired, request reauthentication and
92 // mark data as disabled.
93 if err == plugins.ErrTokenExpired {
94 a.watcher.Refresh(a.authData.AccountId)
95 a.authData.Enabled = false
96 }
83 } else if len(n) > 0 {97 } else if len(n) > 0 {
84 // on success we reset the timeout to the default interval98 // on success we reset the timeout to the default interval
85 a.interval = DEFAULT_INTERVAL99 a.interval = DEFAULT_INTERVAL
@@ -88,7 +102,5 @@
88}102}
89103
90func (a *AccountManager) updateAuthData(authData accounts.AuthData) {104func (a *AccountManager) updateAuthData(authData accounts.AuthData) {
91 a.authMutex.Lock()105 a.authChan <- authData
92 defer a.authMutex.Unlock()
93 a.authData = authData
94}106}
95107
=== modified file 'cmd/account-polld/main.go'
--- cmd/account-polld/main.go 2014-07-18 11:42:52 +0000
+++ cmd/account-polld/main.go 2014-07-24 08:50:37 +0000
@@ -38,9 +38,11 @@
38}38}
3939
40const (40const (
41 SERVICETYPE_POLL = "account-polld"
42
41 SERVICENAME_GMAIL = "google-gmail-poll"43 SERVICENAME_GMAIL = "google-gmail-poll"
42 SERVICENAME_TWITTER = "twitter-microblog"44 SERVICENAME_TWITTER = "twitter-poll"
43 SERVICENAME_FACEBOOK = "facebook-microblog"45 SERVICENAME_FACEBOOK = "facebook-poll"
44)46)
4547
46const (48const (
@@ -69,9 +71,10 @@
69}71}
7072
71func monitorAccounts(postWatch chan *PostWatch) {73func monitorAccounts(postWatch chan *PostWatch) {
74 watcher := accounts.NewWatcher(SERVICETYPE_POLL)
72 mgr := make(map[uint]*AccountManager)75 mgr := make(map[uint]*AccountManager)
73L:76L:
74 for data := range accounts.WatchForService(SERVICENAME_GMAIL, SERVICENAME_FACEBOOK, SERVICENAME_TWITTER) {77 for data := range watcher.C {
75 if account, ok := mgr[data.AccountId]; ok {78 if account, ok := mgr[data.AccountId]; ok {
76 if data.Enabled {79 if data.Enabled {
77 log.Printf("New account data for %d - was %#v, now is %#v", data.AccountId, account.authData, data)80 log.Printf("New account data for %d - was %#v, now is %#v", data.AccountId, account.authData, data)
@@ -98,7 +101,8 @@
98 log.Println("Unhandled account with id", data.AccountId, "for", data.ServiceName)101 log.Println("Unhandled account with id", data.AccountId, "for", data.ServiceName)
99 continue L102 continue L
100 }103 }
101 mgr[data.AccountId] = NewAccountManager(data, postWatch, plugin)104 mgr[data.AccountId] = NewAccountManager(watcher, postWatch, plugin)
105 mgr[data.AccountId].updateAuthData(data)
102 go mgr[data.AccountId].Loop()106 go mgr[data.AccountId].Loop()
103 }107 }
104 }108 }
105109
=== modified file 'cmd/account-watcher-test/main.go'
--- cmd/account-watcher-test/main.go 2014-07-11 12:07:47 +0000
+++ cmd/account-watcher-test/main.go 2014-07-24 08:50:37 +0000
@@ -9,7 +9,11 @@
99
10func main() {10func main() {
11 // Expects a list of service names as command line arguments11 // Expects a list of service names as command line arguments
12 for data := range accounts.WatchForService(os.Args[1:]...) {12 for data := range accounts.NewWatcher(os.Args[1]).C {
13 fmt.Printf("%#v\n", data)13 if data.Error != nil {
14 fmt.Println("Failed to authenticate account", data.AccountId, ":", data.Error)
15 } else {
16 fmt.Printf("%#v\n", data)
17 }
14 }18 }
15}19}
1620
=== modified file 'data/account-polld.application'
--- data/account-polld.application 2014-07-17 19:52:56 +0000
+++ data/account-polld.application 2014-07-24 08:50:37 +0000
@@ -4,15 +4,9 @@
4 <desktop-entry>account-polld.desktop</desktop-entry>4 <desktop-entry>account-polld.desktop</desktop-entry>
5 <translations>account-polld</translations>5 <translations>account-polld</translations>
66
7 <services>7 <service-types>
8 <service id="google-gmail-poll">8 <service-type id="account-polld">
9 <description>Get notifications for your email</description>9 <description>Receive notifications for this account</description>
10 </service>10 </service-type>
11 <service id="facebook-microblog">11 </service-types>
12 <description>Get notifications for facebook</description>
13 </service>
14 <service id="twitter-microblog">
15 <description>Get notifications for twitter</description>
16 </service>
17 </services>
18</application>12</application>
1913
=== added file 'data/account-polld.service-type'
--- data/account-polld.service-type 1970-01-01 00:00:00 +0000
+++ data/account-polld.service-type 2014-07-24 08:50:37 +0000
@@ -0,0 +1,7 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<service-type id="account-polld">
3 <name>Account Polling</name>
4 <description>Integrate with account-polld</description>
5 <icon>intouch</icon>
6 <translations>account-polld</translations>
7</service-type>
08
=== added file 'data/facebook-poll.service'
--- data/facebook-poll.service 1970-01-01 00:00:00 +0000
+++ data/facebook-poll.service 2014-07-24 08:50:37 +0000
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<service id="facebook-poll">
3 <type>account-polld</type>
4 <name>Facebook</name>
5 <icon>facebook</icon>
6 <provider>facebook</provider>
7 <translations>account-polld</translations>
8</service>
09
=== modified file 'data/google-gmail-poll.service'
--- data/google-gmail-poll.service 2014-07-17 19:52:56 +0000
+++ data/google-gmail-poll.service 2014-07-24 08:50:37 +0000
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<service id="google-gmail-poll">2<service id="google-gmail-poll">
3 <type>mail</type>3 <type>account-polld</type>
4 <name>GMail</name>4 <name>GMail</name>
5 <provider>google</provider>5 <provider>google</provider>
6 <icon>google</icon>6 <icon>google</icon>
77
=== added file 'data/twitter-poll.service'
--- data/twitter-poll.service 1970-01-01 00:00:00 +0000
+++ data/twitter-poll.service 2014-07-24 08:50:37 +0000
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<service id="twitter-poll">
3 <type>account-polld</type>
4 <name>Twitter</name>
5 <icon>twitter</icon>
6 <provider>twitter</provider>
7 <translations>account-polld</translations>
8</service>
09
=== modified file 'debian/rules'
--- debian/rules 2014-07-23 18:31:38 +0000
+++ debian/rules 2014-07-24 08:50:37 +0000
@@ -30,11 +30,14 @@
30 mkdir -p \30 mkdir -p \
31 ${CURDIR}/debian/account-polld/usr/share/accounts/application \31 ${CURDIR}/debian/account-polld/usr/share/accounts/application \
32 ${CURDIR}/debian/account-polld/usr/share/accounts/services \32 ${CURDIR}/debian/account-polld/usr/share/accounts/services \
33 ${CURDIR}/debian/account-polld/usr/share/accounts/service_types \
33 ${CURDIR}/debian/account-polld/usr/share/applications34 ${CURDIR}/debian/account-polld/usr/share/applications
34 cp ${CURDIR}/data/account-polld.application \35 cp ${CURDIR}/data/account-polld.application \
35 ${CURDIR}/debian/account-polld/usr/share/accounts/application/36 ${CURDIR}/debian/account-polld/usr/share/accounts/application/
36 cp ${CURDIR}/data/google-gmail-poll.service \37 cp ${CURDIR}/data/*.service \
37 ${CURDIR}/debian/account-polld/usr/share/accounts/services/38 ${CURDIR}/debian/account-polld/usr/share/accounts/services/
39 cp ${CURDIR}/data/account-polld.service-type \
40 ${CURDIR}/debian/account-polld/usr/share/accounts/service_types/
38 cp ${CURDIR}/data/account-polld.desktop \41 cp ${CURDIR}/data/account-polld.desktop \
39 ${CURDIR}/debian/account-polld/usr/share/applications/42 ${CURDIR}/debian/account-polld/usr/share/applications/
4043
4144
=== modified file 'plugins/facebook/facebook.go'
--- plugins/facebook/facebook.go 2014-07-17 18:35:36 +0000
+++ plugins/facebook/facebook.go 2014-07-24 08:50:37 +0000
@@ -62,6 +62,9 @@
62 if err := decoder.Decode(&result); err != nil {62 if err := decoder.Decode(&result); err != nil {
63 return nil, err63 return nil, err
64 }64 }
65 if result.Error.Code == 190 {
66 return nil, plugins.ErrTokenExpired
67 }
65 return nil, &result.Error68 return nil, &result.Error
66 }69 }
6770
6871
=== modified file 'plugins/facebook/facebook_test.go'
--- plugins/facebook/facebook_test.go 2014-07-18 11:38:36 +0000
+++ plugins/facebook/facebook_test.go 2014-07-24 08:50:37 +0000
@@ -23,6 +23,8 @@
23 "testing"23 "testing"
2424
25 . "launchpad.net/gocheck"25 . "launchpad.net/gocheck"
26
27 "launchpad.net/account-polld/plugins"
26)28)
2729
28type S struct{}30type S struct{}
@@ -48,10 +50,18 @@
48 errorBody = `50 errorBody = `
49{51{
50 "error": {52 "error": {
51 "message": "Message describing the error",53 "message": "Unknown path components: /xyz",
54 "type": "OAuthException",
55 "code": 2500
56 }
57}`
58 tokenExpiredErrorBody = `
59{
60 "error": {
61 "message": "Error validating access token: Session has expired",
52 "type": "OAuthException",62 "type": "OAuthException",
53 "code": 190 ,63 "code": 190 ,
54 "error_subcode": 46064 "error_subcode": 463
55 }65 }
56}`66}`
57 notificationsBody = `67 notificationsBody = `
@@ -151,7 +161,17 @@
151 c.Check(notifications, IsNil)161 c.Check(notifications, IsNil)
152 c.Assert(err, Not(IsNil))162 c.Assert(err, Not(IsNil))
153 graphErr := err.(*GraphError)163 graphErr := err.(*GraphError)
154 c.Check(graphErr.Message, Equals, "Message describing the error")164 c.Check(graphErr.Message, Equals, "Unknown path components: /xyz")
155 c.Check(graphErr.Code, Equals, 190)165 c.Check(graphErr.Code, Equals, 2500)
156 c.Check(graphErr.Subcode, Equals, 460)166}
167
168func (s S) TestTokenExpiredErrorResponse(c *C) {
169 resp := &http.Response{
170 StatusCode: http.StatusBadRequest,
171 Body: closeWrapper{bytes.NewReader([]byte(tokenExpiredErrorBody))},
172 }
173 p := &fbPlugin{}
174 notifications, err := p.parseResponse(resp)
175 c.Check(notifications, IsNil)
176 c.Assert(err, Equals, plugins.ErrTokenExpired)
157}177}
158178
=== modified file 'plugins/plugins.go'
--- plugins/plugins.go 2014-07-17 18:35:36 +0000
+++ plugins/plugins.go 2014-07-24 08:50:37 +0000
@@ -17,7 +17,11 @@
1717
18package plugins18package plugins
1919
20import "launchpad.net/account-polld/accounts"20import (
21 "errors"
22
23 "launchpad.net/account-polld/accounts"
24)
2125
22// Plugin is an interface which the plugins will adhere to for the poll26// Plugin is an interface which the plugins will adhere to for the poll
23// daemon to interact with.27// daemon to interact with.
@@ -122,3 +126,7 @@
122 PLUGIN_EMAIL = 0126 PLUGIN_EMAIL = 0
123 PLUGIN_SOCIAL127 PLUGIN_SOCIAL
124)128)
129
130// ErrTokenExpired is the error returned by a plugin to indicate that
131// the web service reported that the authentication token has expired.
132var ErrTokenExpired = errors.New("Token expired")
125133
=== modified file 'plugins/twitter/twitter.go'
--- plugins/twitter/twitter.go 2014-07-23 10:35:13 +0000
+++ plugins/twitter/twitter.go 2014-07-24 08:50:37 +0000
@@ -77,6 +77,12 @@
77 if err := decoder.Decode(&result); err != nil {77 if err := decoder.Decode(&result); err != nil {
78 return nil, err78 return nil, err
79 }79 }
80 // Error code 89 is used for invalid or expired tokens.
81 for _, e := range result.Errors {
82 if e.Code == 89 {
83 return nil, plugins.ErrTokenExpired
84 }
85 }
80 return nil, &result86 return nil, &result
81 }87 }
8288
@@ -113,6 +119,12 @@
113 if err := decoder.Decode(&result); err != nil {119 if err := decoder.Decode(&result); err != nil {
114 return nil, err120 return nil, err
115 }121 }
122 // Error code 89 is used for invalid or expired tokens.
123 for _, e := range result.Errors {
124 if e.Code == 89 {
125 return nil, plugins.ErrTokenExpired
126 }
127 }
116 return nil, &result128 return nil, &result
117 }129 }
118130
119131
=== modified file 'plugins/twitter/twitter_test.go'
--- plugins/twitter/twitter_test.go 2014-07-18 11:37:04 +0000
+++ plugins/twitter/twitter_test.go 2014-07-24 08:50:37 +0000
@@ -23,6 +23,8 @@
23 "testing"23 "testing"
2424
25 . "launchpad.net/gocheck"25 . "launchpad.net/gocheck"
26
27 "launchpad.net/account-polld/plugins"
26)28)
2729
28type S struct{}30type S struct{}
@@ -54,6 +56,15 @@
54 }56 }
55 ]57 ]
56}`58}`
59 tokenExpiredErrorBody = `
60{
61 "errors": [
62 {
63 "message":"Invalid or expired token",
64 "code":89
65 }
66 ]
67}`
57 statusesBody = `68 statusesBody = `
58[69[
59 {70 {
@@ -425,6 +436,17 @@
425 c.Check(twErr.Errors[0].Code, Equals, 34)436 c.Check(twErr.Errors[0].Code, Equals, 34)
426}437}
427438
439func (s S) TestParseStatusesTokenExpiredError(c *C) {
440 resp := &http.Response{
441 StatusCode: http.StatusBadRequest,
442 Body: closeWrapper{bytes.NewReader([]byte(tokenExpiredErrorBody))},
443 }
444 p := &twitterPlugin{}
445 messages, err := p.parseStatuses(resp)
446 c.Check(messages, IsNil)
447 c.Assert(err, Equals, plugins.ErrTokenExpired)
448}
449
428func (s S) TestParseDirectMessages(c *C) {450func (s S) TestParseDirectMessages(c *C) {
429 resp := &http.Response{451 resp := &http.Response{
430 StatusCode: http.StatusOK,452 StatusCode: http.StatusOK,
@@ -453,3 +475,14 @@
453 c.Check(twErr.Errors[0].Message, Equals, "Sorry, that page does not exist")475 c.Check(twErr.Errors[0].Message, Equals, "Sorry, that page does not exist")
454 c.Check(twErr.Errors[0].Code, Equals, 34)476 c.Check(twErr.Errors[0].Code, Equals, 34)
455}477}
478
479func (s S) TestParseDirectMessagesTokenExpiredError(c *C) {
480 resp := &http.Response{
481 StatusCode: http.StatusBadRequest,
482 Body: closeWrapper{bytes.NewReader([]byte(tokenExpiredErrorBody))},
483 }
484 p := &twitterPlugin{}
485 messages, err := p.parseDirectMessages(resp)
486 c.Check(messages, IsNil)
487 c.Assert(err, Equals, plugins.ErrTokenExpired)
488}

Subscribers

People subscribed via source and target branches