Merge lp:~jpds/friends/linkedin-protocol into lp:friends

Proposed by Jonathan Davies
Status: Superseded
Proposed branch: lp:~jpds/friends/linkedin-protocol
Merge into: lp:friends
Diff against target: 1291 lines (+357/-306) (has conflicts)
20 files modified
Makefile (+1/-1)
debian/changelog (+15/-2)
debian/control (+6/-0)
debian/friends-linkedin.install (+1/-0)
friends/protocols/flickr.py (+3/-3)
friends/protocols/linkedin.py (+133/-0)
friends/service/dispatcher.py (+9/-8)
friends/tests/mocks.py (+9/-4)
friends/tests/test_account.py (+29/-107)
friends/tests/test_authentication.py (+33/-39)
friends/tests/test_dispatcher.py (+23/-24)
friends/tests/test_facebook.py (+8/-5)
friends/tests/test_flickr.py (+6/-2)
friends/tests/test_foursquare.py (+4/-0)
friends/tests/test_identica.py (+4/-0)
friends/tests/test_twitter.py (+11/-21)
friends/utils/account.py (+32/-71)
friends/utils/authentication.py (+23/-10)
friends/utils/base.py (+3/-3)
tools/debug_live.py (+4/-6)
Text conflict in debian/changelog
To merge this branch: bzr merge lp:~jpds/friends/linkedin-protocol
Reviewer Review Type Date Requested Status
Robert Bruce Park Needs Resubmitting
Review via email: mp+155194@code.launchpad.net

This proposal has been superseded by a proposal from 2013-03-25.

Commit message

Implemented a base LinkedIn protocol for friends.

Description of the change

Implemented a base LinkedIn protocol for friends.

To post a comment you must log in.
Revision history for this message
Robert Bruce Park (robru) wrote :

MP into wrong branch ;-)

review: Needs Resubmitting
lp:~jpds/friends/linkedin-protocol updated
197. By Jonathan Davies

Gave url .get() a default value for robustness.

198. By Jonathan Davies

Save LinkedIn connections to EDS with _push_to_eds().

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2013-02-05 01:11:35 +0000
3+++ Makefile 2013-03-25 10:33:12 +0000
4@@ -14,7 +14,7 @@
5 # along with this program. If not, see <http://www.gnu.org/licenses/>.
6
7 check:
8- python3 -m unittest discover -vv
9+ python3 -m unittest discover
10
11 install:
12 python3 setup.py install
13
14=== modified file 'debian/changelog'
15--- debian/changelog 2013-03-22 05:02:25 +0000
16+++ debian/changelog 2013-03-25 10:33:12 +0000
17@@ -1,3 +1,4 @@
18+<<<<<<< TREE
19 friends (0.1.3daily13.03.22-0ubuntu1) raring; urgency=low
20
21 * Automatic snapshot from revision 168
22@@ -14,18 +15,30 @@
23
24 -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 21 Mar 2013 05:02:17 +0000
25
26+=======
27+friends (0.2.0-0ubuntu2) UNRELEASED; urgency=low
28+
29+ [ Robert Bruce Park ]
30+ * Version bump for the next development series.
31+
32+ [ Jonathan Davies ]
33+ * Added friends-linkedin.
34+
35+ -- Jonathan Davies <jonathan.davies@canonical.com> Thu, 21 Mar 2013 22:52:07 +0000
36+
37+>>>>>>> MERGE-SOURCE
38 friends (0.1.3daily13.03.20-0ubuntu1) raring; urgency=low
39
40 [ Robert Bruce Park ]
41 * Keep the Dispatcher alive for 30s beyond the return of the final
42 method invocation.
43- * Stop deduplicating messages across protocols, simplifying model
44+ * Stop deduplicating messages across protocols, simplifying model
45 schema (LP: #1156941)
46 * Add schema columns for latitude, longitude, and location name.
47 * Fix 'likes' column from gdouble to guint64.
48 * Add geotagging support from foursquare, facebook, flickr.
49 * Implement since= for Facebook, reducing bandwidth usage.
50- * Automatically prepend the required @mention to Twitter
51+ * Automatically prepend the required @mention to Twitter
52 replies (LP: #1156829)
53 * Automatically linkify URLs that get published to the model.
54 * Fix the publishing of Facebook Stories (LP: #1155785)
55
56=== modified file 'debian/control'
57--- debian/control 2013-02-22 00:06:42 +0000
58+++ debian/control 2013-03-25 10:33:12 +0000
59@@ -103,3 +103,9 @@
60 Depends: friends, ${misc:Depends}, ${python3:Depends}
61 Description: Social integration with the desktop - Flickr
62 Provides social networking integration with the desktop
63+
64+Package: friends-linkedin
65+Architecture: all
66+Depends: friends, ${misc:Depends}, ${python3:Depends}
67+Description: Social integration with the desktop - LinkedIn
68+ Provides social networking integration with the desktop
69
70=== added file 'debian/friends-linkedin.install'
71--- debian/friends-linkedin.install 1970-01-01 00:00:00 +0000
72+++ debian/friends-linkedin.install 2013-03-25 10:33:12 +0000
73@@ -0,0 +1,1 @@
74+usr/lib/python3/dist-packages/friends/protocols/linkedin*
75
76=== modified file 'friends/protocols/flickr.py'
77--- friends/protocols/flickr.py 2013-03-12 21:08:21 +0000
78+++ friends/protocols/flickr.py 2013-03-25 10:33:12 +0000
79@@ -82,7 +82,7 @@
80 # http://www.flickr.com/services/api/flickr.people.getInfo.html
81 def _get_avatar(self, nsid):
82 args = dict(
83- api_key=self._account.auth.parameters.get('ConsumerKey'),
84+ api_key=self._account.consumer_key,
85 method='flickr.people.getInfo',
86 format='json',
87 nojsoncallback='1',
88@@ -109,7 +109,7 @@
89 self._get_access_token()
90
91 args = dict(
92- api_key=self._account.auth.parameters.get('ConsumerKey'),
93+ api_key=self._account.consumer_key,
94 method='flickr.photos.getContactsPhotos',
95 format='json',
96 nojsoncallback='1',
97@@ -179,7 +179,7 @@
98 self._get_access_token()
99
100 args = dict(
101- api_key=self._account.auth.parameters.get('ConsumerKey'),
102+ api_key=self._account.consumer_key,
103 title=title,
104 )
105
106
107=== added file 'friends/protocols/linkedin.py'
108--- friends/protocols/linkedin.py 1970-01-01 00:00:00 +0000
109+++ friends/protocols/linkedin.py 2013-03-25 10:33:12 +0000
110@@ -0,0 +1,133 @@
111+# friends-dispatcher -- send & receive messages from any social network
112+# Copyright (C) 2013 Canonical Ltd
113+#
114+# This program is free software: you can redistribute it and/or modify
115+# it under the terms of the GNU General Public License as published by
116+# the Free Software Foundation, version 3 of the License.
117+#
118+# This program is distributed in the hope that it will be useful,
119+# but WITHOUT ANY WARRANTY; without even the implied warranty of
120+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
121+# GNU General Public License for more details.
122+#
123+# You should have received a copy of the GNU General Public License
124+# along with this program. If not, see <http://www.gnu.org/licenses/>.
125+
126+"""The LinkedIn protocol plugin."""
127+
128+
129+__all__ = [
130+ 'LinkedIn',
131+ ]
132+
133+import time
134+import logging
135+
136+from friends.utils.avatar import Avatar
137+from friends.utils.base import Base, feature
138+from friends.utils.cache import JsonCache
139+from friends.utils.http import Downloader, Uploader
140+from friends.utils.time import parsetime, iso8601utc
141+from friends.errors import FriendsError
142+
143+log = logging.getLogger(__name__)
144+
145+class LinkedIn(Base):
146+ _api_base = 'https://api.linkedin.com/v1/{endpoint}?format=json&secure-urls=true&oauth2_access_token={token}'
147+
148+ def _whoami(self, authdata):
149+ """Identify the authenticating user."""
150+ # http://developer.linkedin.com/documents/profile-fields
151+ url = self._api_base.format(
152+ endpoint='people/~:(id,first-name,last-name)',
153+ token=self._get_access_token())
154+ result = Downloader(url).get_json()
155+ self._account.user_id = result.get('id')
156+ self._account.user_full_name = '{firstName} {lastName}'.format(**result)
157+
158+ def _publish_entry(self, entry, stream='messages'):
159+ """Publish a single update into the Dee.SharedModel."""
160+ message_id = entry.get('updateKey')
161+
162+ if message_id is None:
163+ # We can't do much with this entry.
164+ return
165+
166+ content = entry.get('updateContent')
167+ person = content.get('person')
168+ name = '{firstName} {lastName}'.format(**person)
169+ person_id = person.get('id')
170+ status = person.get('currentStatus')
171+ picture = person.get('pictureUrl', '')
172+ url = person.get('siteStandardProfileRequest').get('url')
173+ timestamp = entry.get('timestamp')
174+ # We need to divide by 1000 here, as LinkedIn's timestamps have
175+ # milliseconds.
176+ iso_time = iso8601utc(int(timestamp/1000))
177+
178+ # Posts gives us a likes dict, while replies give us an int.
179+ likes = entry.get('numLikes', 0)
180+
181+ args = dict(
182+ message_id=message_id,
183+ stream=stream,
184+ message=status,
185+ likes=likes,
186+ sender_id=person_id,
187+ sender=name,
188+ icon_uri=picture,
189+ link_url=url,
190+ timestamp=iso_time
191+ )
192+
193+ self._publish(**args)
194+
195+ @feature
196+ def home(self):
197+ """Gather and publish public timeline messages."""
198+ url = self._api_base.format(
199+ endpoint='people/~/network/updates',
200+ token=self._get_access_token()) + '&type=STAT'
201+ result = Downloader(url).get_json()
202+ values = result.get('values')
203+ for update in values:
204+ self._publish_entry(update)
205+
206+ @feature
207+ def receive(self):
208+ """Gather and publish all incoming messages."""
209+ self.home()
210+ return self._get_n_rows()
211+
212+ def _create_contact(self, connection_json):
213+ """Build a VCard based on a dict representation of a contact."""
214+ user_id = connection_json.get('id')
215+
216+ user_fullname = '{firstName} {lastName}'.format(**connection_json)
217+ user_link = connection_json.get('siteStandardProfileRequest').get('url')
218+
219+ attrs = {}
220+ attrs['linkined-id'] = user_id
221+ attrs['linkedin-name'] = user_fullname
222+ attrs['X-URIS'] = user_link
223+
224+ contact = Base._create_contact(
225+ self, user_fullname, None, attrs)
226+
227+ return contact
228+
229+ @feature
230+ def contacts(self):
231+ """Retrieve a list of up to 500 LinkedIn connections."""
232+ # http://developer.linkedin.com/documents/connections-api
233+ url = self._api_base.format(
234+ endpoint='people/~/connections',
235+ token=self._get_access_token())
236+ result = Downloader(url).get_json()
237+ connections = result.get('values')
238+
239+ for connection in connections:
240+ if connection.get('id') != 'private':
241+ # We cannot access information on profiles that are set to
242+ # private.
243+ self._create_contact(connection)
244
245=== modified file 'friends/service/dispatcher.py'
246--- friends/service/dispatcher.py 2013-03-20 01:19:39 +0000
247+++ friends/service/dispatcher.py 2013-03-25 10:33:12 +0000
248@@ -31,7 +31,7 @@
249 from contextlib import ContextDecorator
250
251 from friends.utils.avatar import Avatar
252-from friends.utils.account import AccountManager
253+from friends.utils.account import find_accounts
254 from friends.utils.manager import protocol_manager
255 from friends.utils.menus import MenuManager
256 from friends.utils.model import Model, persist_model
257@@ -96,7 +96,8 @@
258 bus_name = dbus.service.BusName(DBUS_INTERFACE, bus=self.bus)
259 super().__init__(bus_name, self.__dbus_object_path__)
260 self.mainloop = mainloop
261- self.account_manager = AccountManager()
262+
263+ self.accounts = find_accounts()
264
265 self._unread_count = 0
266 self.menu_manager = MenuManager(self.Refresh, self.mainloop.quit)
267@@ -119,7 +120,7 @@
268 # account.protocol() starts a new thread and then returns
269 # immediately, so there is no delay or blocking during the
270 # execution of this method.
271- for account in self.account_manager.get_all():
272+ for account in self.accounts.values():
273 try:
274 account.protocol('receive')
275 except NotImplementedError:
276@@ -163,14 +164,14 @@
277 service.Do('list', '6', 'list_id') # Fetch a single list.
278 """
279 if account_id:
280- accounts = [self.account_manager.get(account_id)]
281+ accounts = [self.accounts.get(int(account_id))]
282 if None in accounts:
283 message = 'Could not find account: {}'.format(account_id)
284 failure(message)
285 log.error(message)
286 return
287 else:
288- accounts = list(self.account_manager.get_all())
289+ accounts = list(self.accounts.values())
290
291 called = False
292 for account in accounts:
293@@ -205,7 +206,7 @@
294 service.SendMessage('Your message')
295 """
296 sent = False
297- for account in self.account_manager.get_all():
298+ for account in self.accounts.values():
299 if account.send_enabled:
300 sent = True
301 log.debug(
302@@ -240,7 +241,7 @@
303 service.SendReply('6', '34245645347345626', 'Your reply')
304 """
305 log.debug('Replying to {}, {}'.format(account_id, message_id))
306- account = self.account_manager.get(account_id)
307+ account = self.accounts.get(int(account_id))
308 if account is not None:
309 account.protocol(
310 'send_thread',
311@@ -298,7 +299,7 @@
312 free to ignore error conditions at your peril.
313 """
314 log.debug('Uploading {} to {}'.format(uri, account_id))
315- account = self.account_manager.get(account_id)
316+ account = self.accounts.get(int(account_id))
317 if account is not None:
318 account.protocol(
319 'upload',
320
321=== modified file 'friends/tests/mocks.py'
322--- friends/tests/mocks.py 2013-03-14 19:44:13 +0000
323+++ friends/tests/mocks.py 2013-03-25 10:33:12 +0000
324@@ -100,16 +100,21 @@
325
326
327 class FakeAuth:
328- id = 'fakeauth id'
329- method = 'fakeauth method'
330- parameters = {'ConsumerKey': 'fake', 'ConsumerSecret': 'alsofake'}
331- mechanism = 'fakeauth mechanism'
332+ get_credentials_id = lambda *ignore: 'fakeauth id'
333+ get_method = lambda *ignore: 'fakeauth method'
334+ get_mechanism = lambda *ignore: 'fakeauth mechanism'
335+ get_parameters = lambda *ignore: {
336+ 'ConsumerKey': 'fake',
337+ 'ConsumerSecret': 'alsofake',
338+ }
339
340
341 class FakeAccount:
342 """A fake account object for testing purposes."""
343
344 def __init__(self, service=None, account_id=88):
345+ self.consumer_secret = 'secret'
346+ self.consumer_key = 'consume'
347 self.access_token = None
348 self.secret_token = None
349 self.user_full_name = None
350
351=== modified file 'friends/tests/test_account.py'
352--- friends/tests/test_account.py 2013-03-14 19:14:03 +0000
353+++ friends/tests/test_account.py 2013-03-25 10:33:12 +0000
354@@ -17,7 +17,6 @@
355
356 __all__ = [
357 'TestAccount',
358- 'TestAccountManager',
359 ]
360
361
362@@ -26,14 +25,15 @@
363 from friends.errors import UnsupportedProtocolError
364 from friends.protocols.flickr import Flickr
365 from friends.tests.mocks import FakeAccount, LogMock, SettingsIterMock
366-from friends.tests.mocks import TestModel, mock
367-from friends.utils.account import Account, AccountManager
368+from friends.tests.mocks import TestModel, LogMock, mock
369+from friends.utils.account import Account, _find_accounts_uoa
370
371
372 class TestAccount(unittest.TestCase):
373 """Test Account class."""
374
375 def setUp(self):
376+ self.log_mock = LogMock('friends.utils.account')
377 def connect_side_effect(signal, callback, account):
378 # The account service provides a .connect method that connects a
379 # signal to a callback. We have to mock a side effect into the
380@@ -49,7 +49,9 @@
381 'get_credentials_id.return_value': 'fake credentials',
382 'get_method.return_value': 'fake method',
383 'get_mechanism.return_value': 'fake mechanism',
384- 'get_parameters.return_value': 'fake parameters',
385+ 'get_parameters.return_value': {
386+ 'ConsumerKey': 'fake_key',
387+ 'ConsumerSecret': 'fake_secret'},
388 }),
389 'get_account.return_value': mock.Mock(**{
390 'get_settings_iter.return_value': SettingsIterMock(),
391@@ -63,17 +65,21 @@
392 })
393 self.account = Account(self.account_service)
394
395+ def tearDown(self):
396+ self.log_mock.stop()
397+
398 def test_account_auth(self):
399 # Test that the constructor initializes the 'auth' attribute.
400 auth = self.account.auth
401- self.assertEqual(auth.id, 'fake credentials')
402- self.assertEqual(auth.method, 'fake method')
403- self.assertEqual(auth.mechanism, 'fake mechanism')
404- self.assertEqual(auth.parameters, 'fake parameters')
405+ self.assertEqual(auth.get_credentials_id(), 'fake credentials')
406+ self.assertEqual(auth.get_method(), 'fake method')
407+ self.assertEqual(auth.get_mechanism(), 'fake mechanism')
408+ self.assertEqual(auth.get_parameters(),
409+ dict(ConsumerKey='fake_key',
410+ ConsumerSecret='fake_secret'))
411
412 def test_account_id(self):
413 self.assertEqual(self.account.id, 'fake_id')
414- self.assertEqual(self.account.protocol_name, 'flickr')
415
416 def test_account_service(self):
417 # The protocol attribute refers directly to the protocol used.
418@@ -122,102 +128,18 @@
419 self.assertFalse(hasattr(self.account, 'bee'))
420 self.assertFalse(hasattr(self.account, 'cat'))
421
422- def test_enabled(self):
423- # .enabled() just passes through from the account service.
424- self.account_service.get_enabled.return_value = True
425- self.assertTrue(self.account.enabled)
426- self.account_service.get_enabled.return_value = False
427- self.assertFalse(self.account.enabled)
428-
429- def test_equal(self):
430- # Two accounts are equal if their account services are equal.
431- other = Account(self.account_service)
432- self.assertEqual(self.account, other)
433- assert not self.account == None
434-
435- def test_unequal(self):
436- # Two accounts are unequal if their account services are unequal. The
437- # other mock service has to at least support the basic required API.
438- other = Account(mock.Mock(**{
439- 'get_account.return_value': mock.Mock(**{
440- 'get_settings_iter.return_value': SettingsIterMock(),
441- # It's okay if the provider names are the same; the test
442- # is for whether the account services are the same or not,
443- # and in this test, they'll be different mock instances.
444- 'get_provider_name.return_value': 'flickr',
445- }),
446- }))
447- self.assertNotEqual(self.account, other)
448- assert self.account != None
449-
450-
451-accounts_manager = mock.Mock()
452-accounts_manager.new_for_service_type(
453- 'microblogging').get_enabled_account_services.return_value = []
454-
455-
456-@mock.patch('gi.repository.Accounts.Manager', accounts_manager)
457-@mock.patch('friends.utils.account.Account', FakeAccount)
458-class TestAccountManager(unittest.TestCase):
459- """Test the AccountManager API."""
460-
461- def setUp(self):
462- TestModel.clear()
463- self.account_service = mock.Mock()
464-
465+ @mock.patch('friends.utils.account.manager')
466+ @mock.patch('friends.utils.account.Account')
467 @mock.patch('friends.utils.account.Accounts')
468- def test_get_service(self, accounts_mock):
469- manager = AccountManager()
470- manager_mock = mock.Mock()
471- account_mock = mock.Mock()
472- service_mock = mock.Mock()
473- manager_mock.get_account.return_value = account_mock
474- account_mock.list_services.return_value = [service_mock]
475- account_service_mock = accounts_mock.AccountService.new(account_mock,
476- service_mock)
477- account_service_mock.get_service(
478- ).get_display_name().lower.return_value = 'protocol'
479-
480- service = manager._get_service(manager_mock, 10)
481-
482- manager_mock.get_account.assert_called_once_with(10)
483- account_mock.list_services.assert_called_once_with()
484- accounts_mock.AccountService.new.assert_called_with(account_mock,
485- service_mock)
486-
487- def test_account_manager_add_new_account(self):
488- # Explicitly adding a new account puts the account's global_id into
489- # the account manager's mapping.
490- manager = AccountManager()
491- manager._add_new_account(self.account_service)
492- self.assertIn(88, manager._accounts)
493-
494- def test_account_manager_enabled_event(self):
495- manager = AccountManager()
496- manager._get_service = mock.Mock()
497- manager._get_service.return_value = mock.Mock()
498- manager._add_new_account = mock.Mock()
499- manager._add_new_account.return_value = account = mock.Mock()
500- manager._on_enabled_event(accounts_manager, 2)
501- account.protocol.assert_called_once_with('receive')
502-
503-
504-@mock.patch('gi.repository.Accounts.Manager', accounts_manager)
505-class TestAccountManagerRealAccount(unittest.TestCase):
506- """Test of the AccountManager API requiring the real Account class.
507-
508- You'll need to guarantee other mocks are in place such that the real
509- accounts are not touched.
510- """
511- def setUp(self):
512- self.account_service = mock.Mock()
513-
514- def test_account_manager_add_new_account_unsupported(self):
515- fake_account = self.account_service.get_account()
516- fake_account.get_provider_name.return_value = 'no service'
517- manager = AccountManager()
518- with LogMock('friends.utils.account') as log_mock:
519- manager._add_new_account(self.account_service)
520- log_contents = log_mock.empty(trim=False)
521- self.assertNotIn('no service', manager._accounts)
522- self.assertEqual(log_contents, 'Unsupported protocol: no service\n')
523+ def test_find_accounts(self, accts, acct, manager):
524+ service = mock.Mock()
525+ get_enabled = manager.get_enabled_account_services
526+ get_enabled.return_value = [service]
527+ manager.reset_mock()
528+ accounts = _find_accounts_uoa()
529+ get_enabled.assert_called_once_with()
530+ acct.assert_called_once_with(service)
531+ self.assertEqual(accounts, {acct().id: acct()})
532+ self.assertEqual(self.log_mock.empty(),
533+ 'Flickr (fake_id) got send_enabled: True\n'
534+ 'Accounts found: 1\n')
535
536=== modified file 'friends/tests/test_authentication.py'
537--- friends/tests/test_authentication.py 2013-02-05 01:11:35 +0000
538+++ friends/tests/test_authentication.py 2013-03-25 10:33:12 +0000
539@@ -27,7 +27,7 @@
540 import unittest
541
542 from friends.utils.authentication import Authentication
543-from friends.tests.mocks import FakeAccount, mock
544+from friends.tests.mocks import FakeAccount, LogMock, mock
545 from friends.errors import AuthorizationError
546
547
548@@ -43,7 +43,11 @@
549 # error, and user_data arguments. We'll use the parameters
550 # argument as a way to specify whether an error occurred during
551 # authentication or not.
552- callback(None, self.results, parameters, None)
553+ callback(
554+ None,
555+ self.results,
556+ parameters if hasattr(parameters, 'message') else None,
557+ None)
558
559
560 class FakeSignon:
561@@ -56,59 +60,49 @@
562 results = dict(NoAccessToken='fail')
563
564
565-class Logger:
566- def __init__(self):
567- self.debug_messages = []
568- self.error_messages = []
569-
570- def debug(self, message, *args):
571- self.debug_messages.append(message.format(*args))
572-
573- def error(self, message, *args):
574- self.error_messages.append(message.format(*args))
575-
576- reset = __init__
577-
578-
579-logger = Logger()
580-
581-
582 class TestAuthentication(unittest.TestCase):
583 """Test authentication."""
584
585 def setUp(self):
586+ self.log_mock = LogMock('friends.utils.authentication')
587 self.account = FakeAccount()
588- self.account.auth.id = 'my id'
589- self.account.auth.method = 'some method'
590- self.account.auth.parameters = 'change me'
591- self.account.auth.mechanism = ['whatever']
592- logger.reset()
593-
594- @mock.patch('friends.utils.authentication.log', logger)
595+ self.account.auth.get_credentials_id = lambda *ignore: 'my id'
596+ self.account.auth.get_method = lambda *ignore: 'some method'
597+ self.account.auth.get_parameters = lambda *ignore: 'change me'
598+ self.account.auth.get_mechanism = lambda *ignore: 'whatever'
599+
600+ def tearDown(self):
601+ self.log_mock.stop()
602+
603+ @mock.patch('friends.utils.authentication.manager')
604 @mock.patch('friends.utils.authentication.Signon', FakeSignon)
605- def test_successful_login(self):
606+ @mock.patch('friends.utils.authentication.Accounts')
607+ def test_successful_login(self, accounts, *mocks):
608 # Prevent an error in the callback.
609- self.account.auth.parameters = False
610- authenticator = Authentication(self.account)
611+ accounts.AccountService.new().get_auth_data(
612+ ).get_parameters.return_value = False
613+ authenticator = Authentication(self.account.id)
614 reply = authenticator.login()
615 self.assertEqual(reply, dict(AccessToken='auth reply'))
616- self.assertEqual(logger.debug_messages, ['Login completed'])
617- self.assertEqual(logger.error_messages, [])
618+ self.assertEqual(self.log_mock.empty(), 'Login completed\n')
619
620- @mock.patch('friends.utils.authentication.log', logger)
621+ @mock.patch('friends.utils.authentication.manager')
622+ @mock.patch('friends.utils.authentication.Accounts')
623 @mock.patch('friends.utils.authentication.Signon', FailingSignon)
624- def test_missing_access_token(self):
625+ def test_missing_access_token(self, *mocks):
626 # Prevent an error in the callback.
627- self.account.auth.parameters = False
628- authenticator = Authentication(self.account)
629+ self.account.auth.get_parameters = lambda *ignore: False
630+ authenticator = Authentication(self.account.id)
631 self.assertRaises(AuthorizationError, authenticator.login)
632
633- @mock.patch('friends.utils.authentication.log', logger)
634+ @mock.patch('friends.utils.authentication.manager')
635 @mock.patch('friends.utils.authentication.Signon', FakeSignon)
636- def test_failed_login(self):
637+ @mock.patch('friends.utils.authentication.Accounts')
638+ def test_failed_login(self, accounts, *mocks):
639 # Trigger an error in the callback.
640 class Error:
641 message = 'who are you?'
642- self.account.auth.parameters = Error
643- authenticator = Authentication(self.account)
644+ accounts.AccountService.new(
645+ ).get_auth_data().get_parameters.return_value = Error
646+ authenticator = Authentication(self.account.id)
647 self.assertRaises(AuthorizationError, authenticator.login)
648
649=== modified file 'friends/tests/test_dispatcher.py'
650--- friends/tests/test_dispatcher.py 2013-03-20 01:19:39 +0000
651+++ friends/tests/test_dispatcher.py 2013-03-25 10:33:12 +0000
652@@ -38,13 +38,13 @@
653 """Test the dispatcher's ability to dispatch."""
654
655 @mock.patch('dbus.service.BusName')
656- @mock.patch('friends.service.dispatcher.AccountManager')
657- @mock.patch('friends.service.dispatcher.Dispatcher.Refresh')
658+ @mock.patch('friends.service.dispatcher.find_accounts')
659 @mock.patch('dbus.service.Object.__init__')
660 def setUp(self, *mocks):
661 self.log_mock = LogMock('friends.service.dispatcher',
662 'friends.utils.account')
663 self.dispatcher = Dispatcher(mock.Mock(), mock.Mock())
664+ self.dispatcher.accounts = {}
665
666 def tearDown(self):
667 self.log_mock.stop()
668@@ -53,12 +53,12 @@
669 def test_refresh(self, threading_mock):
670 account = mock.Mock()
671 threading_mock.activeCount.return_value = 1
672- self.dispatcher.account_manager = mock.Mock()
673- self.dispatcher.account_manager.get_all.return_value = [account]
674+ self.dispatcher.accounts = mock.Mock()
675+ self.dispatcher.accounts.values.return_value = [account]
676
677 self.assertIsNone(self.dispatcher.Refresh())
678
679- self.dispatcher.account_manager.get_all.assert_called_once_with()
680+ self.dispatcher.accounts.values.assert_called_once_with()
681 account.protocol.assert_called_once_with('receive')
682
683 self.assertEqual(self.log_mock.empty(),
684@@ -75,12 +75,11 @@
685 def test_do(self):
686 account = mock.Mock()
687 account.id = '345'
688- self.dispatcher.account_manager = mock.Mock()
689- self.dispatcher.account_manager.get.return_value = account
690+ self.dispatcher.accounts = mock.Mock()
691+ self.dispatcher.accounts.get.return_value = account
692
693 self.dispatcher.Do('like', '345', '23346356767354626')
694- self.dispatcher.account_manager.get.assert_called_once_with(
695- '345')
696+ self.dispatcher.accounts.get.assert_called_once_with(345)
697 account.protocol.assert_called_once_with(
698 'like', '23346356767354626', success=STUB, failure=STUB)
699
700@@ -92,11 +91,11 @@
701
702 def test_failing_do(self):
703 account = mock.Mock()
704- self.dispatcher.account_manager = mock.Mock()
705- self.dispatcher.account_manager.get.return_value = None
706+ self.dispatcher.accounts = mock.Mock()
707+ self.dispatcher.accounts.get.return_value = None
708
709 self.dispatcher.Do('unlike', '6', '23346356767354626')
710- self.dispatcher.account_manager.get.assert_called_once_with('6')
711+ self.dispatcher.accounts.get.assert_called_once_with(6)
712 self.assertEqual(account.protocol.call_count, 0)
713
714 self.assertEqual(self.log_mock.empty(),
715@@ -111,15 +110,15 @@
716 account3 = mock.Mock()
717 account2.send_enabled = False
718
719- self.dispatcher.account_manager = mock.Mock()
720- self.dispatcher.account_manager.get_all.return_value = [
721+ self.dispatcher.accounts = mock.Mock()
722+ self.dispatcher.accounts.values.return_value = [
723 account1,
724 account2,
725 account3,
726 ]
727
728 self.dispatcher.SendMessage('Howdy friends!')
729- self.dispatcher.account_manager.get_all.assert_called_once_with()
730+ self.dispatcher.accounts.values.assert_called_once_with()
731 account1.protocol.assert_called_once_with(
732 'send', 'Howdy friends!', success=STUB, failure=STUB)
733 account3.protocol.assert_called_once_with(
734@@ -128,11 +127,11 @@
735
736 def test_send_reply(self):
737 account = mock.Mock()
738- self.dispatcher.account_manager = mock.Mock()
739- self.dispatcher.account_manager.get.return_value = account
740+ self.dispatcher.accounts = mock.Mock()
741+ self.dispatcher.accounts.get.return_value = account
742
743 self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]')
744- self.dispatcher.account_manager.get.assert_called_once_with('2')
745+ self.dispatcher.accounts.get.assert_called_once_with(2)
746 account.protocol.assert_called_once_with(
747 'send_thread', 'objid', '[Hilarious Response]',
748 success=STUB, failure=STUB)
749@@ -145,11 +144,11 @@
750
751 def test_send_reply_failed(self):
752 account = mock.Mock()
753- self.dispatcher.account_manager = mock.Mock()
754- self.dispatcher.account_manager.get.return_value = None
755+ self.dispatcher.accounts = mock.Mock()
756+ self.dispatcher.accounts.get.return_value = None
757
758 self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]')
759- self.dispatcher.account_manager.get.assert_called_once_with('2')
760+ self.dispatcher.accounts.get.assert_called_once_with(2)
761 self.assertEqual(account.protocol.call_count, 0)
762
763 self.assertEqual(self.log_mock.empty(),
764@@ -161,8 +160,8 @@
765
766 def test_upload_async(self):
767 account = mock.Mock()
768- self.dispatcher.account_manager = mock.Mock()
769- self.dispatcher.account_manager.get.return_value = account
770+ self.dispatcher.accounts = mock.Mock()
771+ self.dispatcher.accounts.get.return_value = account
772
773 success = mock.Mock()
774 failure = mock.Mock()
775@@ -172,7 +171,7 @@
776 'A thousand words',
777 success=success,
778 failure=failure)
779- self.dispatcher.account_manager.get.assert_called_once_with('2')
780+ self.dispatcher.accounts.get.assert_called_once_with(2)
781 account.protocol.assert_called_once_with(
782 'upload',
783 'file://path/to/image.png',
784
785=== modified file 'friends/tests/test_facebook.py'
786--- friends/tests/test_facebook.py 2013-03-16 00:54:45 +0000
787+++ friends/tests/test_facebook.py 2013-03-25 10:33:12 +0000
788@@ -60,11 +60,13 @@
789 ['contacts', 'delete', 'home', 'like', 'receive', 'search', 'send',
790 'send_thread', 'unlike', 'upload', 'wall'])
791
792+ @mock.patch('friends.utils.authentication.manager')
793+ @mock.patch('friends.utils.authentication.Accounts')
794 @mock.patch('friends.utils.authentication.Authentication.login',
795 return_value=dict(AccessToken='abc'))
796 @mock.patch('friends.utils.http.Soup.Message',
797 FakeSoupMessage('friends.tests.data', 'facebook-login.dat'))
798- def test_successful_login(self, mock):
799+ def test_successful_login(self, *mocks):
800 # Test that a successful response from graph.facebook.com returning
801 # the user's data, sets up the account dict correctly.
802 self.protocol._login()
803@@ -72,14 +74,18 @@
804 self.assertEqual(self.account.user_name, 'Bart Person')
805 self.assertEqual(self.account.user_id, '801')
806
807+ @mock.patch('friends.utils.authentication.manager')
808+ @mock.patch('friends.utils.authentication.Accounts')
809 @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1)
810 @mock.patch('friends.utils.authentication.Signon.AuthSession.new')
811- def test_login_unsuccessful_authentication(self, mock):
812+ def test_login_unsuccessful_authentication(self, *mocks):
813 # The user is not already logged in, but the act of logging in fails.
814 self.assertRaises(AuthorizationError, self.protocol._login)
815 self.assertIsNone(self.account.access_token)
816 self.assertIsNone(self.account.user_name)
817
818+ @mock.patch('friends.utils.authentication.manager')
819+ @mock.patch('friends.utils.authentication.Accounts')
820 @mock.patch('friends.utils.authentication.Authentication.login',
821 return_value=dict(AccessToken='abc'))
822 @mock.patch('friends.protocols.facebook.Downloader.get_json',
823@@ -237,9 +243,6 @@
824 def test_home_since_id(self, *mocks):
825 self.account.access_token = 'access'
826 self.account.secret_token = 'secret'
827- self.account.auth.parameters = dict(
828- ConsumerKey='key',
829- ConsumerSecret='secret')
830 self.assertEqual(self.protocol.home(), 12)
831
832 with open(self._root.format('facebook_ids'), 'r') as fd:
833
834=== modified file 'friends/tests/test_flickr.py'
835--- friends/tests/test_flickr.py 2013-03-20 23:57:40 +0000
836+++ friends/tests/test_flickr.py 2013-03-25 10:33:12 +0000
837@@ -86,6 +86,8 @@
838 # But also no photos.
839 self.assertEqual(TestModel.get_n_rows(), 0)
840
841+ @mock.patch('friends.utils.authentication.manager')
842+ @mock.patch('friends.utils.authentication.Accounts')
843 @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1)
844 @mock.patch('friends.utils.authentication.Signon.AuthSession.new')
845 @mock.patch('friends.utils.http.Soup.Message',
846@@ -95,6 +97,8 @@
847 # AccessToken, but this fails.
848 self.assertRaises(AuthorizationError, self.protocol.receive)
849
850+ @mock.patch('friends.utils.authentication.manager')
851+ @mock.patch('friends.utils.authentication.Accounts')
852 @mock.patch('friends.utils.http.Soup.Message',
853 FakeSoupMessage('friends.tests.data', 'flickr-nophotos.dat'))
854 @mock.patch('friends.utils.authentication.Authentication.login',
855@@ -102,7 +106,7 @@
856 user_nsid='bob',
857 AccessToken='123',
858 TokenSecret='abc'))
859- def test_login_successful_authentication(self, mock):
860+ def test_login_successful_authentication(self, *mocks):
861 # Logging in required communication with the account service to get an
862 # AccessToken, but this fails.
863 self.protocol.receive()
864@@ -131,7 +135,7 @@
865 extras='date_upload,owner_name,icon_server,geo',
866 format='json',
867 nojsoncallback='1',
868- api_key='fake',
869+ api_key='consume',
870 method='flickr.photos.getContactsPhotos',
871 ),
872 headers={})
873
874=== modified file 'friends/tests/test_foursquare.py'
875--- friends/tests/test_foursquare.py 2013-03-14 19:14:03 +0000
876+++ friends/tests/test_foursquare.py 2013-03-25 10:33:12 +0000
877@@ -50,6 +50,8 @@
878 # The set of public features.
879 self.assertEqual(FourSquare.get_features(), ['receive'])
880
881+ @mock.patch('friends.utils.authentication.manager')
882+ @mock.patch('friends.utils.authentication.Accounts')
883 @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1)
884 @mock.patch('friends.utils.authentication.Signon.AuthSession.new')
885 @mock.patch('friends.utils.http.Downloader.get_json',
886@@ -59,6 +61,8 @@
887 self.assertIsNone(self.account.user_name)
888 self.assertIsNone(self.account.user_id)
889
890+ @mock.patch('friends.utils.authentication.manager')
891+ @mock.patch('friends.utils.authentication.Accounts')
892 @mock.patch('friends.utils.authentication.Authentication.login',
893 return_value=dict(AccessToken='tokeny goodness'))
894 @mock.patch('friends.protocols.foursquare.Downloader.get_json',
895
896=== modified file 'friends/tests/test_identica.py'
897--- friends/tests/test_identica.py 2013-03-14 19:14:03 +0000
898+++ friends/tests/test_identica.py 2013-03-25 10:33:12 +0000
899@@ -53,6 +53,8 @@
900 self.log_mock.stop()
901 shutil.rmtree(self._temp_cache)
902
903+ @mock.patch('friends.utils.authentication.manager')
904+ @mock.patch('friends.utils.authentication.Accounts')
905 @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1)
906 @mock.patch('friends.utils.authentication.Signon.AuthSession.new')
907 @mock.patch('friends.utils.http.Downloader.get_json',
908@@ -62,6 +64,8 @@
909 self.assertIsNone(self.account.user_name)
910 self.assertIsNone(self.account.user_id)
911
912+ @mock.patch('friends.utils.authentication.manager')
913+ @mock.patch('friends.utils.authentication.Accounts')
914 @mock.patch('friends.utils.authentication.Authentication.login',
915 return_value=dict(AccessToken='some clever fake data',
916 TokenSecret='sssssshhh!'))
917
918=== modified file 'friends/tests/test_twitter.py'
919--- friends/tests/test_twitter.py 2013-03-19 04:17:58 +0000
920+++ friends/tests/test_twitter.py 2013-03-25 10:33:12 +0000
921@@ -57,15 +57,19 @@
922 self.log_mock.stop()
923 shutil.rmtree(self._temp_cache)
924
925+ @mock.patch('friends.utils.authentication.manager')
926+ @mock.patch('friends.utils.authentication.Accounts')
927 @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1)
928 @mock.patch('friends.utils.authentication.Signon.AuthSession.new')
929 @mock.patch('friends.protocols.twitter.Downloader.get_json',
930 return_value=None)
931- def test_unsuccessful_authentication(self, dload, login):
932+ def test_unsuccessful_authentication(self, dload, login, *mocks):
933 self.assertRaises(AuthorizationError, self.protocol._login)
934 self.assertIsNone(self.account.user_name)
935 self.assertIsNone(self.account.user_id)
936
937+ @mock.patch('friends.utils.authentication.manager')
938+ @mock.patch('friends.utils.authentication.Accounts')
939 @mock.patch('friends.utils.authentication.Authentication.login',
940 return_value=dict(AccessToken='some clever fake data',
941 TokenSecret='sssssshhh!',
942@@ -86,11 +90,12 @@
943 def test_signatures(self, dload):
944 self.account.secret_token = 'alpha'
945 self.account.access_token = 'omega'
946- self.account.auth.id = 6
947- self.account.auth.method = 'oauth2'
948- self.account.auth.mechanism = 'HMAC-SHA1'
949- self.account.auth.parameters = dict(ConsumerKey='consume',
950- ConsumerSecret='obey')
951+ self.account.consumer_secret = 'obey'
952+ self.account.consumer_key = 'consume'
953+ self.account.auth.get_credentials_id = lambda *ignore: 6
954+ self.account.auth.get_method = lambda *ignore: 'oauth2'
955+ self.account.auth.get_mechanism = lambda *ignore: 'HMAC-SHA1'
956+
957 result = '''\
958 OAuth oauth_nonce="once%20upon%20a%20nonce", \
959 oauth_timestamp="1348690628", \
960@@ -122,9 +127,6 @@
961 def test_home(self, *mocks):
962 self.account.access_token = 'access'
963 self.account.secret_token = 'secret'
964- self.account.auth.parameters = dict(
965- ConsumerKey='key',
966- ConsumerSecret='secret')
967 self.assertEqual(0, TestModel.get_n_rows())
968 self.assertEqual(self.protocol.home(), 3)
969 self.assertEqual(3, TestModel.get_n_rows())
970@@ -171,9 +173,6 @@
971 def test_home_since_id(self, *mocks):
972 self.account.access_token = 'access'
973 self.account.secret_token = 'secret'
974- self.account.auth.parameters = dict(
975- ConsumerKey='key',
976- ConsumerSecret='secret')
977 self.assertEqual(self.protocol.home(), 3)
978
979 with open(self._root.format('twitter_ids'), 'r') as fd:
980@@ -196,9 +195,6 @@
981 self.account.access_token = 'access'
982 self.account.secret_token = 'secret'
983 self.account.user_name = 'oauth_dancer'
984- self.account.auth.parameters = dict(
985- ConsumerKey='key',
986- ConsumerSecret='secret')
987 self.assertEqual(0, TestModel.get_n_rows())
988 self.assertEqual(
989 self.protocol.send('some message'),
990@@ -399,9 +395,6 @@
991 def test_send_thread_prepend_nick(self, *mocks):
992 self.account.access_token = 'access'
993 self.account.secret_token = 'secret'
994- self.account.auth.parameters = dict(
995- ConsumerKey='key',
996- ConsumerSecret='secret')
997 self.assertEqual(0, TestModel.get_n_rows())
998 self.assertEqual(self.protocol.home(), 3)
999 self.assertEqual(3, TestModel.get_n_rows())
1000@@ -703,9 +696,6 @@
1001 def test_protocol_rate_limiting(self, time, sleep, login):
1002 self.account.access_token = 'access'
1003 self.account.secret_token = 'secret'
1004- self.account.auth.parameters = dict(
1005- ConsumerKey='key',
1006- ConsumerSecret='secret')
1007 # Test rate limiting via the Twitter plugin API.
1008 #
1009 # The first call doesn't get rate limited.
1010
1011=== modified file 'friends/utils/account.py'
1012--- friends/utils/account.py 2013-03-13 02:03:12 +0000
1013+++ friends/utils/account.py 2013-03-25 10:33:12 +0000
1014@@ -17,7 +17,7 @@
1015
1016 __all__ = [
1017 'Account',
1018- 'AccountManager',
1019+ 'find_accounts',
1020 ]
1021
1022
1023@@ -28,65 +28,30 @@
1024
1025 from friends.errors import UnsupportedProtocolError
1026 from friends.utils.manager import protocol_manager
1027+from friends.utils.authentication import manager
1028
1029
1030 log = logging.getLogger(__name__)
1031
1032
1033-class AccountManager:
1034- """Manage the accounts that we know about."""
1035-
1036- def __init__(self):
1037- self._accounts = {}
1038- # Ask libaccounts for a manager of the microblogging services.
1039- # Connect callbacks to the manager so that we can react when accounts
1040- # are added or deleted.
1041- manager = Accounts.Manager.new_for_service_type('microblogging')
1042- manager.connect('enabled-event', self._on_enabled_event)
1043- # Add all the currently known accounts.
1044- for account_service in manager.get_enabled_account_services():
1045- self._add_new_account(account_service)
1046- log.info('Accounts found: {}'.format(len(self._accounts)))
1047-
1048- def _get_service(self, manager, account_id):
1049- """Instantiate an AccountService and identify it."""
1050- account = manager.get_account(account_id)
1051- for service in account.list_services():
1052- return Accounts.AccountService.new(account, service)
1053-
1054- def _on_enabled_event(self, manager, account_id):
1055- """React to new microblogging accounts being enabled or disabled."""
1056- account_service = self._get_service(manager, account_id)
1057- if account_service is not None and account_service.get_enabled():
1058- log.debug('Adding account {}'.format(account_id))
1059- account = self._add_new_account(account_service)
1060- if account is not None:
1061- account.protocol('receive')
1062-
1063- def _add_new_account(self, account_service):
1064+def _find_accounts_uoa():
1065+ """Consult Ubuntu Online Accounts for the accounts we have."""
1066+ accounts = {}
1067+ for service in manager.get_enabled_account_services():
1068 try:
1069- new_account = Account(account_service)
1070+ account = Account(service)
1071 except UnsupportedProtocolError as error:
1072 log.info(error)
1073 else:
1074- self._accounts[new_account.id] = new_account
1075- return new_account
1076-
1077- def get_all(self):
1078- return self._accounts.values()
1079-
1080- def get(self, account_id, default=None):
1081- return self._accounts.get(int(account_id), default)
1082-
1083-
1084-class AuthData:
1085- """This class serves as a sub-namespace for Account instances."""
1086-
1087- def __init__(self, auth_data):
1088- self.id = auth_data.get_credentials_id()
1089- self.method = auth_data.get_method()
1090- self.mechanism = auth_data.get_mechanism()
1091- self.parameters = auth_data.get_parameters()
1092+ accounts[account.id] = account
1093+ log.info('Accounts found: {}'.format(len(accounts)))
1094+ return accounts
1095+
1096+
1097+def find_accounts():
1098+ # TODO: Implement GOA support, then fill out this method with some
1099+ # logic for determining whether to use UOA or GOA.
1100+ return _find_accounts_uoa()
1101
1102
1103 class Account:
1104@@ -99,6 +64,8 @@
1105 )
1106
1107 # Defaults for the known and useful attributes.
1108+ consumer_secret = None
1109+ consumer_key = None
1110 access_token = None
1111 secret_token = None
1112 send_enabled = None
1113@@ -108,15 +75,22 @@
1114 id = None
1115
1116 def __init__(self, account_service):
1117- self.account_service = account_service
1118- self.auth = AuthData(account_service.get_auth_data())
1119+ self.auth = account_service.get_auth_data()
1120+ if self.auth is not None:
1121+ auth_params = self.auth.get_parameters()
1122+ self.consumer_key = auth_params.get('ConsumerKey')
1123+ self.consumer_secret = auth_params.get('ConsumerSecret')
1124+ else:
1125+ raise UnsupportedProtocolError(
1126+ 'This AgAccountService is missing AgAuthData!')
1127+
1128 # The provider in libaccounts should match the name of our protocol.
1129 account = account_service.get_account()
1130 self.id = account.id
1131- self.protocol_name = account.get_provider_name()
1132- protocol_class = protocol_manager.protocols.get(self.protocol_name)
1133+ protocol_name = account.get_provider_name()
1134+ protocol_class = protocol_manager.protocols.get(protocol_name)
1135 if protocol_class is None:
1136- raise UnsupportedProtocolError(self.protocol_name)
1137+ raise UnsupportedProtocolError(protocol_name)
1138 self.protocol = protocol_class(self)
1139 # Connect responders to changes in the account information.
1140 account_service.connect('changed', self._on_account_changed, account)
1141@@ -152,24 +126,11 @@
1142 while True:
1143 success, key, value = settings.next()
1144 if success:
1145- log.debug('{} got {}: {}'.format(self.id, key, value))
1146+ log.debug('{} ({}) got {}: {}'.format(
1147+ self.protocol._Name, self.id, key, value))
1148 # Testing for tuple membership makes this easy to expand
1149 # later, if necessary.
1150 if key in Account._LIBACCOUNTS_PROPERTIES:
1151 setattr(self, key, value)
1152 else:
1153 break
1154-
1155- @property
1156- def enabled(self):
1157- return self.account_service.get_enabled()
1158-
1159- def __eq__(self, other):
1160- if other is None:
1161- return False
1162- return self.account_service == other.account_service
1163-
1164- def __ne__(self, other):
1165- if other is None:
1166- return True
1167- return self.account_service != other.account_service
1168
1169=== modified file 'friends/utils/authentication.py'
1170--- friends/utils/authentication.py 2013-03-13 17:34:26 +0000
1171+++ friends/utils/authentication.py 2013-03-25 10:33:12 +0000
1172@@ -23,7 +23,7 @@
1173 import logging
1174 import time
1175
1176-from gi.repository import GObject, Signon
1177+from gi.repository import GObject, Accounts, Signon
1178
1179 from friends.errors import AuthorizationError
1180
1181@@ -37,17 +37,30 @@
1182 LOGIN_TIMEOUT = 30 # Currently this is measured in half-seconds.
1183
1184
1185+# Yes, this is not the most logical place to instantiate this, but I
1186+# couldn't do it in account.py due to cyclical import dependencies.
1187+manager = Accounts.Manager.new_for_service_type('microblogging')
1188+
1189+
1190 class Authentication:
1191- def __init__(self, account):
1192- self.account = account
1193+ def __init__(self, account_id):
1194+ self.account_id = account_id
1195+ account = manager.get_account(account_id)
1196+ service = account.list_services()[0]
1197+ self.auth = Accounts.AccountService.new(
1198+ account, service).get_auth_data()
1199 self._reply = None
1200
1201 def login(self):
1202- auth = self.account.auth
1203- self.auth_session = Signon.AuthSession.new(auth.id, auth.method)
1204+ auth = self.auth
1205+ self.auth_session = Signon.AuthSession.new(
1206+ auth.get_credentials_id(),
1207+ auth.get_method())
1208 self.auth_session.process(
1209- auth.parameters, auth.mechanism,
1210- self._login_cb, None)
1211+ auth.get_parameters(),
1212+ auth.get_mechanism(),
1213+ self._login_cb,
1214+ None)
1215 timeout = LOGIN_TIMEOUT
1216 while self._reply is None and timeout > 0:
1217 # We're building a synchronous API on top of an inherently
1218@@ -56,17 +69,17 @@
1219 time.sleep(0.5)
1220 timeout -= 1
1221 if self._reply is None:
1222- raise AuthorizationError(self.account.id, 'Login timed out.')
1223+ raise AuthorizationError(self.account_id, 'Login timed out.')
1224 if 'AccessToken' not in self._reply:
1225 raise AuthorizationError(
1226- self.account.id,
1227+ self.account_id,
1228 'No AccessToken found: {!r}'.format(self._reply))
1229 return self._reply
1230
1231 def _login_cb(self, session, reply, error, user_data):
1232 self._reply = reply
1233 if error:
1234- exception = AuthorizationError(self.account.id, error.message)
1235+ exception = AuthorizationError(self.account_id, error.message)
1236 # Mardy says this error can happen during normal operation.
1237 if error.message.endswith('userActionFinished error: 10'):
1238 log.error(str(exception))
1239
1240=== modified file 'friends/utils/base.py'
1241--- friends/utils/base.py 2013-03-20 01:19:39 +0000
1242+++ friends/utils/base.py 2013-03-25 10:33:12 +0000
1243@@ -460,7 +460,7 @@
1244 log.debug('{} to {}'.format(
1245 'Re-authenticating' if old_token else 'Logging in', self._Name))
1246
1247- result = Authentication(self._account).login()
1248+ result = Authentication(self._account.id).login()
1249
1250 self._account.access_token = result.get('AccessToken')
1251 self._whoami(result)
1252@@ -469,8 +469,8 @@
1253 def _get_oauth_headers(self, method, url, data=None, headers=None):
1254 """Basic wrapper around oauthlib that we use for Twitter and Flickr."""
1255 # "Client" == "Consumer" in oauthlib parlance.
1256- client_key = self._account.auth.parameters['ConsumerKey']
1257- client_secret = self._account.auth.parameters['ConsumerSecret']
1258+ client_key = self._account.consumer_key
1259+ client_secret = self._account.consumer_secret
1260
1261 # "resource_owner" == secret and token.
1262 resource_owner_key = self._get_access_token()
1263
1264=== modified file 'tools/debug_live.py'
1265--- tools/debug_live.py 2013-03-13 18:05:13 +0000
1266+++ tools/debug_live.py 2013-03-25 10:33:12 +0000
1267@@ -30,7 +30,7 @@
1268 # Print all logs for debugging purposes
1269 initialize(debug=True, console=True)
1270
1271-from friends.utils.account import AccountManager
1272+from friends.utils.account import find_accounts
1273 from friends.utils.base import initialize_caches, _OperationThread
1274 from friends.utils.model import Model
1275
1276@@ -53,12 +53,10 @@
1277
1278 initialize_caches()
1279
1280+ Model.connect('row-added', row_added)
1281+
1282 found = False
1283- a = AccountManager()
1284-
1285- Model.connect('row-added', row_added)
1286-
1287- for account in a._accounts.values():
1288+ for account in find_accounts().values():
1289 if account.protocol._name == protocol.lower():
1290 found = True
1291 account.protocol(*args)

Subscribers

People subscribed via source and target branches

to all changes: