Merge lp:~robru/friends/avatar-cache into lp:friends

Proposed by Robert Bruce Park
Status: Merged
Approved by: Ken VanDine
Approved revision: 208
Merged at revision: 208
Proposed branch: lp:~robru/friends/avatar-cache
Merge into: lp:friends
Diff against target: 554 lines (+56/-137)
15 files modified
friends/protocols/facebook.py (+4/-6)
friends/protocols/flickr.py (+4/-7)
friends/protocols/foursquare.py (+1/-3)
friends/protocols/twitter.py (+1/-3)
friends/service/dispatcher.py (+0/-9)
friends/service/mock_service.py (+0/-4)
friends/tests/test_avatars.py (+0/-29)
friends/tests/test_facebook.py (+5/-10)
friends/tests/test_flickr.py (+2/-6)
friends/tests/test_foursquare.py (+3/-4)
friends/tests/test_notify.py (+19/-0)
friends/tests/test_twitter.py (+10/-15)
friends/utils/avatar.py (+4/-24)
friends/utils/notify.py (+3/-1)
service/src/service.vala (+0/-16)
To merge this branch: bzr merge lp:~robru/friends/avatar-cache
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Ken VanDine Approve
Review via email: mp+170206@code.launchpad.net

Commit message

Move avatar cache under /tmp

Description of the change

Stop caching avatar URLs locally; change icon_uri model schema to contain the original URL rather than the local cache. Delete all avatar cache expiry logic. Move the avatar cache under /tmp so that the system will clear it at each boot.

How to test this change:

1. build a package from this branch and install it.
2. delete ~/.local/share/resources/com.canonical.Friends.Streams
3. start friends-app
4. ensure that friends-app scrolls very smoothly through the list of tweets
5. watch out for notifications, ensure that the avatar icon appears in them. (maybe set com.canonical.friends.notifications to 'all' so that you're more likely to see a notification when you do a refresh).

Oh, and of course, run 'make check' ;-)

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:208
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~robru/friends/avatar-cache/+merge/170206/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/friends-ci/46/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-saucy-amd64-ci/3

Click here to trigger a rebuild:
http://s-jenkins:8080/job/friends-ci/46/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Looks great and works well, thanks!

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'friends/protocols/facebook.py'
--- friends/protocols/facebook.py 2013-06-17 21:01:36 +0000
+++ friends/protocols/facebook.py 2013-06-18 22:10:33 +0000
@@ -24,7 +24,6 @@
24import time24import time
25import logging25import logging
2626
27from friends.utils.avatar import Avatar
28from friends.utils.base import Base, feature27from friends.utils.base import Base, feature
29from friends.utils.cache import JsonCache28from friends.utils.cache import JsonCache
30from friends.utils.http import Downloader, Uploader29from friends.utils.http import Downloader, Uploader
@@ -99,8 +98,8 @@
99 args['sender_id'] = sender_id = from_record.get('id', '')98 args['sender_id'] = sender_id = from_record.get('id', '')
100 args['url'] = STORY_PERMALINK.format(99 args['url'] = STORY_PERMALINK.format(
101 id=sender_id, post_id=post_id)100 id=sender_id, post_id=post_id)
102 args['icon_uri'] = Avatar.get_image(101 args['icon_uri'] = (API_BASE.format(id=sender_id) +
103 API_BASE.format(id=sender_id) + '/picture?width=840&height=840')102 '/picture?width=840&height=840')
104 args['sender_nick'] = from_record.get('name', '')103 args['sender_nick'] = from_record.get('name', '')
105 args['from_me'] = (sender_id == self._account.user_id)104 args['from_me'] = (sender_id == self._account.user_id)
106105
@@ -322,9 +321,8 @@
322 sender_nick=self._account.user_name,321 sender_nick=self._account.user_name,
323 timestamp=iso8601utc(int(time.time())),322 timestamp=iso8601utc(int(time.time())),
324 url=destination_url,323 url=destination_url,
325 icon_uri=Avatar.get_image(324 icon_uri=(API_BASE.format(id=self._account.user_id) +
326 API_BASE.format(id=self._account.user_id) +325 '/picture?type=large'))
327 '/picture?type=large'))
328 return destination_url326 return destination_url
329 else:327 else:
330 raise FriendsError(str(response))328 raise FriendsError(str(response))
331329
=== modified file 'friends/protocols/flickr.py'
--- friends/protocols/flickr.py 2013-03-25 23:37:08 +0000
+++ friends/protocols/flickr.py 2013-06-18 22:10:33 +0000
@@ -25,7 +25,6 @@
25import time25import time
26import logging26import logging
2727
28from friends.utils.avatar import Avatar
29from friends.utils.base import Base, feature28from friends.utils.base import Base, feature
30from friends.utils.http import Downloader, Uploader29from friends.utils.http import Downloader, Uploader
31from friends.utils.time import iso8601utc, parsetime30from friends.utils.time import iso8601utc, parsetime
@@ -93,13 +92,11 @@
93 iconfarm = person.get('iconfarm')92 iconfarm = person.get('iconfarm')
94 iconserver = person.get('iconserver')93 iconserver = person.get('iconserver')
95 if None in (iconfarm, iconserver):94 if None in (iconfarm, iconserver):
96 return Avatar.get_image(95 return 'http://www.flickr.com/images/buddyicon.gif'
97 'http://www.flickr.com/images/buddyicon.gif')96 return BUDDY_ICON_URL.format(
98 avatar = BUDDY_ICON_URL.format(
99 farm=iconfarm,97 farm=iconfarm,
100 server=iconserver,98 server=iconserver,
101 nsid=nsid)99 nsid=nsid)
102 return Avatar.get_image(avatar)
103100
104# http://www.flickr.com/services/api/flickr.photos.getContactsPhotos.html101# http://www.flickr.com/services/api/flickr.photos.getContactsPhotos.html
105 @feature102 @feature
@@ -135,8 +132,8 @@
135 url = ''132 url = ''
136 from_me = (ownername == username)133 from_me = (ownername == username)
137 if None not in (icon_farm, icon_server, owner):134 if None not in (icon_farm, icon_server, owner):
138 icon_uri = Avatar.get_image(BUDDY_ICON_URL.format(135 icon_uri = BUDDY_ICON_URL.format(
139 farm=icon_farm, server=icon_server, nsid=owner))136 farm=icon_farm, server=icon_server, nsid=owner)
140 url = IMAGE_PAGE_URL.format(owner=owner, photo=photo_id)137 url = IMAGE_PAGE_URL.format(owner=owner, photo=photo_id)
141138
142 # Calculate the ISO 8601 UTC time string.139 # Calculate the ISO 8601 UTC time string.
143140
=== modified file 'friends/protocols/foursquare.py'
--- friends/protocols/foursquare.py 2013-03-12 04:04:07 +0000
+++ friends/protocols/foursquare.py 2013-06-18 22:10:33 +0000
@@ -22,7 +22,6 @@
2222
23import logging23import logging
2424
25from friends.utils.avatar import Avatar
26from friends.utils.base import Base, feature25from friends.utils.base import Base, feature
27from friends.utils.http import Downloader26from friends.utils.http import Downloader
28from friends.utils.time import iso8601utc27from friends.utils.time import iso8601utc
@@ -81,7 +80,6 @@
81 for checkin in checkins:80 for checkin in checkins:
82 user = checkin.get('user', {})81 user = checkin.get('user', {})
83 avatar = user.get('photo', {})82 avatar = user.get('photo', {})
84 avatar_url = '{prefix}100x100{suffix}'.format(**avatar)
85 checkin_id = checkin.get('id', '')83 checkin_id = checkin.get('id', '')
86 tz_offset = checkin.get('timeZoneOffset', 0)84 tz_offset = checkin.get('timeZoneOffset', 0)
87 epoch = checkin.get('createdAt', 0)85 epoch = checkin.get('createdAt', 0)
@@ -95,7 +93,7 @@
95 timestamp=iso8601utc(epoch, tz_offset),93 timestamp=iso8601utc(epoch, tz_offset),
96 message=checkin.get('shout', ''),94 message=checkin.get('shout', ''),
97 likes=checkin.get('likes', {}).get('count', 0),95 likes=checkin.get('likes', {}).get('count', 0),
98 icon_uri=Avatar.get_image(avatar_url),96 icon_uri='{prefix}100x100{suffix}'.format(**avatar),
99 url=venue.get('canonicalUrl', ''),97 url=venue.get('canonicalUrl', ''),
100 location=venue.get('name', ''),98 location=venue.get('name', ''),
101 latitude=location.get('lat', 0.0),99 latitude=location.get('lat', 0.0),
102100
=== modified file 'friends/protocols/twitter.py'
--- friends/protocols/twitter.py 2013-04-17 06:13:49 +0000
+++ friends/protocols/twitter.py 2013-06-18 22:10:33 +0000
@@ -27,7 +27,6 @@
2727
28from urllib.parse import quote28from urllib.parse import quote
2929
30from friends.utils.avatar import Avatar
31from friends.utils.base import Base, feature30from friends.utils.base import Base, feature
32from friends.utils.cache import JsonCache31from friends.utils.cache import JsonCache
33from friends.utils.http import BaseRateLimiter, Downloader32from friends.utils.http import BaseRateLimiter, Downloader
@@ -142,8 +141,7 @@
142 sender_id=str(user.get('id', '')),141 sender_id=str(user.get('id', '')),
143 sender_nick=screen_name,142 sender_nick=screen_name,
144 from_me=(screen_name == self._account.user_name),143 from_me=(screen_name == self._account.user_name),
145 icon_uri=Avatar.get_image(144 icon_uri=avatar_url.replace('_normal.', '.'),
146 avatar_url.replace('_normal.', '.')),
147 liked=tweet.get('favorited', False),145 liked=tweet.get('favorited', False),
148 url=permalink,146 url=permalink,
149 )147 )
150148
=== modified file 'friends/service/dispatcher.py'
--- friends/service/dispatcher.py 2013-04-02 21:54:24 +0000
+++ friends/service/dispatcher.py 2013-06-18 22:10:33 +0000
@@ -30,7 +30,6 @@
30from gi.repository import GLib30from gi.repository import GLib
31from contextlib import ContextDecorator31from contextlib import ContextDecorator
3232
33from friends.utils.avatar import Avatar
34from friends.utils.account import find_accounts33from friends.utils.account import find_accounts
35from friends.utils.manager import protocol_manager34from friends.utils.manager import protocol_manager
36from friends.utils.menus import MenuManager35from friends.utils.menus import MenuManager
@@ -347,11 +346,3 @@
347 if not self.settings.get_boolean('shorten-urls'):346 if not self.settings.get_boolean('shorten-urls'):
348 return message347 return message
349 return Short(service_name).sub(message)348 return Short(service_name).sub(message)
350
351 @exit_after_idle
352 @dbus.service.method(DBUS_INTERFACE)
353 def ExpireAvatars(self):
354 pass
355 # Disabled because we don't currently have a way of re-fetching
356 # expired avatars if they're needed again later.
357 # Avatar.expire_old_avatars()
358349
=== modified file 'friends/service/mock_service.py'
--- friends/service/mock_service.py 2013-03-13 04:36:33 +0000
+++ friends/service/mock_service.py 2013-06-18 22:10:33 +0000
@@ -95,7 +95,3 @@
95 @dbus.service.method(DBUS_INTERFACE, in_signature='s', out_signature='s')95 @dbus.service.method(DBUS_INTERFACE, in_signature='s', out_signature='s')
96 def URLShorten(self, url):96 def URLShorten(self, url):
97 return str(len(url))97 return str(len(url))
98
99 @dbus.service.method(DBUS_INTERFACE)
100 def ExpireAvatars(self):
101 pass
10298
=== modified file 'friends/tests/test_avatars.py'
--- friends/tests/test_avatars.py 2013-02-28 19:10:20 +0000
+++ friends/tests/test_avatars.py 2013-06-18 22:10:33 +0000
@@ -130,32 +130,3 @@
130 # This is the PNG file format magic number, living in the first 8130 # This is the PNG file format magic number, living in the first 8
131 # bytes of the file.131 # bytes of the file.
132 self.assertEqual(raw.read(8), bytes.fromhex('89504E470D0A1A0A'))132 self.assertEqual(raw.read(8), bytes.fromhex('89504E470D0A1A0A'))
133
134 def test_cache_expiration(self):
135 # Cache files which are more than 4 weeks old get expired.
136 #
137 # Start by copying two copies of ubuntu.png to the temporary cache
138 # dir. Fiddle with their mtimes. so that one is just younger than 4
139 # weeks old and one is just older than 4 weeks old. Run the cache
140 # eviction method and ensure that the young one is retained while the
141 # old one is removed.
142 with mock.patch('friends.utils.avatar.CACHE_DIR',
143 self._avatar_cache) as cache_dir:
144 os.makedirs(cache_dir)
145 src = resource_filename('friends.tests.data', 'ubuntu.png')
146 aaa = os.path.join(cache_dir, 'aaa')
147 shutil.copyfile(src, aaa)
148 bbb = os.path.join(cache_dir, 'bbb')
149 shutil.copyfile(src, bbb)
150 # Leave the atime unchanged.
151 four_weeks_ago = date.today() - timedelta(weeks=4)
152 young = four_weeks_ago + timedelta(days=1)
153 old = four_weeks_ago - timedelta(days=1)
154 # aaa will be young enough to keep (i.e. 4 weeks less one day ago)
155 os.utime(aaa,
156 (os.stat(aaa).st_atime, time.mktime(young.timetuple())))
157 # bbb will be too old to keep (i.e. 4 weeks plus one day ago)
158 os.utime(bbb,
159 (os.stat(bbb).st_atime, time.mktime(old.timetuple())))
160 Avatar.expire_old_avatars()
161 self.assertEqual(os.listdir(cache_dir), ['aaa'])
162133
=== modified file 'friends/tests/test_facebook.py'
--- friends/tests/test_facebook.py 2013-06-11 19:58:29 +0000
+++ friends/tests/test_facebook.py 2013-06-18 22:10:33 +0000
@@ -129,8 +129,7 @@
129 'Writing code that supports geotagging data from facebook. ' +129 'Writing code that supports geotagging data from facebook. ' +
130 'If y\'all could make some geotagged facebook posts for me ' +130 'If y\'all could make some geotagged facebook posts for me ' +
131 'to test with, that\'d be super.',131 'to test with, that\'d be super.',
132 GLib.get_user_cache_dir() +132 'https://graph.facebook.com/56789/picture?width=840&height=840',
133 '/friends/avatars/5c4e74c64b1a09343558afc1046c2b1d176a2ba2',
134 'https://www.facebook.com/56789/posts/postid1',133 'https://www.facebook.com/56789/posts/postid1',
135 1,134 1,
136 False,135 False,
@@ -155,8 +154,7 @@
155 False,154 False,
156 '2013-03-12T23:29:45Z',155 '2013-03-12T23:29:45Z',
157 'don\'t know how',156 'don\'t know how',
158 GLib.get_user_cache_dir() +157 'https://graph.facebook.com/234/picture?width=840&height=840',
159 '/friends/avatars/9b9379ccc7948e4804dff7914bfa4c6de3974df5',
160 'https://www.facebook.com/234/posts/commentid2',158 'https://www.facebook.com/234/posts/commentid2',
161 0,159 0,
162 False,160 False,
@@ -186,8 +184,7 @@
186 'Texas. Monday, March 11th, 4:00pm to 7:00 pm. Also here ' +184 'Texas. Monday, March 11th, 4:00pm to 7:00 pm. Also here ' +
187 'Hannah Hart (My Drunk Kitchen) and Angry Video Game Nerd ' +185 'Hannah Hart (My Drunk Kitchen) and Angry Video Game Nerd ' +
188 'producer, Sean Keegan. Stanley is in the lobby.',186 'producer, Sean Keegan. Stanley is in the lobby.',
189 GLib.get_user_cache_dir() +187 'https://graph.facebook.com/161247843901324/picture?width=840&height=840',
190 '/friends/avatars/5b2d70e788df790b9c8db4c6a138fc4a1f433ec9',
191 'https://www.facebook.com/161247843901324/posts/629147610444676',188 'https://www.facebook.com/161247843901324/posts/629147610444676',
192 84,189 84,
193 False,190 False,
@@ -213,8 +210,7 @@
213 False,210 False,
214 '2013-03-15T19:57:14Z',211 '2013-03-15T19:57:14Z',
215 'Guy Frenchie did some things with some stuff.',212 'Guy Frenchie did some things with some stuff.',
216 GLib.get_user_cache_dir() +213 'https://graph.facebook.com/1244414/picture?width=840&height=840',
217 '/friends/avatars/3f5e276af0c43f6411d931b829123825ede1968e',
218 'https://www.facebook.com/1244414/posts/100085049977',214 'https://www.facebook.com/1244414/posts/100085049977',
219 3,215 3,
220 False,216 False,
@@ -370,8 +366,7 @@
370 timestamp='2012-11-06T13:49:08Z',366 timestamp='2012-11-06T13:49:08Z',
371 sender_id=None,367 sender_id=None,
372 from_me=True,368 from_me=True,
373 icon_uri=GLib.get_user_cache_dir() +369 icon_uri='https://graph.facebook.com/None/picture?type=large',
374 '/friends/avatars/d49d72a384d50adf7c736ba27ca55bfa9fa5782d',
375 message='This is Ubuntu!',370 message='This is Ubuntu!',
376 message_id='234125',371 message_id='234125',
377 sender=None)372 sender=None)
378373
=== modified file 'friends/tests/test_flickr.py'
--- friends/tests/test_flickr.py 2013-03-25 23:37:08 +0000
+++ friends/tests/test_flickr.py 2013-06-18 22:10:33 +0000
@@ -22,8 +22,6 @@
2222
23import unittest23import unittest
2424
25from gi.repository import GLib
26
27from friends.errors import AuthorizationError, FriendsError25from friends.errors import AuthorizationError, FriendsError
28from friends.protocols.flickr import Flickr26from friends.protocols.flickr import Flickr
29from friends.tests.mocks import FakeAccount, FakeSoupMessage, LogMock27from friends.tests.mocks import FakeAccount, FakeSoupMessage, LogMock
@@ -176,8 +174,7 @@
176 True,174 True,
177 '2013-03-12T19:51:42Z',175 '2013-03-12T19:51:42Z',
178 'Chocolate chai #yegcoffee',176 'Chocolate chai #yegcoffee',
179 GLib.get_user_cache_dir() +177 'http://farm1.static.flickr.com/93/buddyicons/47303164@N00.jpg',
180 '/friends/avatars/7b30ff0140dd9b80f2b1782a2802c3ce785fa0ce',
181 'http://www.flickr.com/photos/47303164@N00/8552892154',178 'http://www.flickr.com/photos/47303164@N00/8552892154',
182 0,179 0,
183 False,180 False,
@@ -204,8 +201,7 @@
204 True,201 True,
205 '2013-03-12T13:54:10Z',202 '2013-03-12T13:54:10Z',
206 'St. Michael - The Archangel',203 'St. Michael - The Archangel',
207 GLib.get_user_cache_dir() +204 'http://farm3.static.flickr.com/2047/buddyicons/27204141@N05.jpg',
208 '/friends/avatars/cae2939354a33fea5f008df91bb8e25920be5dc3',
209 'http://www.flickr.com/photos/27204141@N05/8550829193',205 'http://www.flickr.com/photos/27204141@N05/8550829193',
210 0,206 0,
211 False,207 False,
212208
=== modified file 'friends/tests/test_foursquare.py'
--- friends/tests/test_foursquare.py 2013-03-21 21:49:26 +0000
+++ friends/tests/test_foursquare.py 2013-06-18 22:10:33 +0000
@@ -83,8 +83,6 @@
83 FakeSoupMessage('friends.tests.data', 'foursquare-full.dat'))83 FakeSoupMessage('friends.tests.data', 'foursquare-full.dat'))
84 @mock.patch('friends.protocols.foursquare.FourSquare._login',84 @mock.patch('friends.protocols.foursquare.FourSquare._login',
85 return_value=True)85 return_value=True)
86 @mock.patch('friends.protocols.foursquare.Avatar.get_image',
87 return_value='~/.cache/friends/avatar/hash')
88 def test_receive(self, *mocks):86 def test_receive(self, *mocks):
89 self.account.access_token = 'tokeny goodness'87 self.account.access_token = 'tokeny goodness'
90 self.assertEqual(0, TestModel.get_n_rows())88 self.assertEqual(0, TestModel.get_n_rows())
@@ -94,8 +92,9 @@
94 'foursquare', 88, '50574c9ce4b0a9a6e84433a0',92 'foursquare', 88, '50574c9ce4b0a9a6e84433a0',
95 'messages', 'Jimbob Smith', '', '', True, '2012-09-17T19:15:24Z',93 'messages', 'Jimbob Smith', '', '', True, '2012-09-17T19:15:24Z',
96 "Working on friends's foursquare plugin.",94 "Working on friends's foursquare plugin.",
97 '~/.cache/friends/avatar/hash', '', 0, False, '', '', '',95 'https://irs0.4sqi.net/img/user/100x100/5IEW3VIX55BBEXAO.jpg',
98 '', '', '', 'Pop Soda\'s Coffee House & Gallery',96 '', 0, False, '', '', '', '', '', '',
97 'Pop Soda\'s Coffee House & Gallery',
99 49.88873164336725, -97.158043384552,98 49.88873164336725, -97.158043384552,
100 ]99 ]
101 self.assertEqual(list(TestModel.get_row(0)), expected)100 self.assertEqual(list(TestModel.get_row(0)), expected)
102101
=== modified file 'friends/tests/test_notify.py'
--- friends/tests/test_notify.py 2013-04-16 17:55:01 +0000
+++ friends/tests/test_notify.py 2013-06-18 22:10:33 +0000
@@ -40,6 +40,25 @@
4040
41 @mock.patch('friends.utils.base.Model', TestModel)41 @mock.patch('friends.utils.base.Model', TestModel)
42 @mock.patch('friends.utils.base._seen_ids', {})42 @mock.patch('friends.utils.base._seen_ids', {})
43 @mock.patch('friends.utils.notify.Avatar')
44 @mock.patch('friends.utils.notify.GdkPixbuf')
45 @mock.patch('friends.utils.notify.Notify')
46 def test_publish_avatar_cache(self, notify, gdkpixbuf, avatar):
47 Base._do_notify = lambda protocol, stream: True
48 base = Base(FakeAccount())
49 base._publish(
50 message='http://example.com!',
51 message_id='1234',
52 sender='Benjamin',
53 timestamp=RIGHT_NOW,
54 icon_uri='http://example.com/bob.jpg',
55 )
56 avatar.get_image.assert_called_once_with('http://example.com/bob.jpg')
57 gdkpixbuf.Pixbuf.new_from_file_at_size.assert_called_once_with(
58 avatar.get_image(), 48, 48)
59
60 @mock.patch('friends.utils.base.Model', TestModel)
61 @mock.patch('friends.utils.base._seen_ids', {})
43 @mock.patch('friends.utils.base.notify')62 @mock.patch('friends.utils.base.notify')
44 def test_publish_no_html(self, notify):63 def test_publish_no_html(self, notify):
45 Base._do_notify = lambda protocol, stream: True64 Base._do_notify = lambda protocol, stream: True
4665
=== modified file 'friends/tests/test_twitter.py'
--- friends/tests/test_twitter.py 2013-04-17 06:13:49 +0000
+++ friends/tests/test_twitter.py 2013-06-18 22:10:33 +0000
@@ -26,7 +26,6 @@
26import unittest26import unittest
27import shutil27import shutil
2828
29from gi.repository import GLib
30from urllib.error import HTTPError29from urllib.error import HTTPError
3130
32from friends.protocols.twitter import RateLimiter, Twitter31from friends.protocols.twitter import RateLimiter, Twitter
@@ -138,8 +137,7 @@
138 ['twitter', 88, '240558470661799936',137 ['twitter', 88, '240558470661799936',
139 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', False,138 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', False,
140 '2012-08-28T21:16:23Z', 'just another test',139 '2012-08-28T21:16:23Z', 'just another test',
141 GLib.get_user_cache_dir() +140 'https://si0.twimg.com/profile_images/730275945/oauth-dancer.jpg',
142 '/friends/avatars/ded4ba3c00583ee511f399d0b2537731ca14c39d',
143 'https://twitter.com/oauth_dancer/status/240558470661799936',141 'https://twitter.com/oauth_dancer/status/240558470661799936',
144 0, False, '', '', '', '', '', '', '', 0.0, 0.0,142 0, False, '', '', '', '', '', '', '', 0.0, 0.0,
145 ],143 ],
@@ -149,8 +147,8 @@
149 'with twitter" class at @cal with @othman '147 'with twitter" class at @cal with @othman '
150 '<a href="http://blogs.ischool.berkeley.edu/i290-abdt-s12/">'148 '<a href="http://blogs.ischool.berkeley.edu/i290-abdt-s12/">'
151 'http://blogs.ischool.berkeley.edu/i290-abdt-s12/</a>',149 'http://blogs.ischool.berkeley.edu/i290-abdt-s12/</a>',
152 GLib.get_user_cache_dir() +150 'https://si0.twimg.com/profile_images/1270234259/'
153 '/friends/avatars/0219effc03a3049a622476e6e001a4014f33dc31',151 'raffi-headshot-casual.png',
154 'https://twitter.com/raffi/status/240556426106372096',152 'https://twitter.com/raffi/status/240556426106372096',
155 0, False, '', '', '', '', '', '', '', 0.0, 0.0,153 0, False, '', '', '', '', '', '', '', 0.0, 0.0,
156 ],154 ],
@@ -158,8 +156,8 @@
158 'messages', 'Taylor Singletary', '819797', 'episod', False,156 'messages', 'Taylor Singletary', '819797', 'episod', False,
159 '2012-08-28T19:59:34Z',157 '2012-08-28T19:59:34Z',
160 'You\'d be right more often if you thought you were wrong.',158 'You\'d be right more often if you thought you were wrong.',
161 GLib.get_user_cache_dir() +159 'https://si0.twimg.com/profile_images/2546730059/'
162 '/friends/avatars/0c829cb2934ad76489be21ee5e103735d9b7b034',160 'f6a8zq58mg1hn0ha8vie.jpeg',
163 'https://twitter.com/episod/status/240539141056638977',161 'https://twitter.com/episod/status/240539141056638977',
164 0, False, '', '', '', '', '', '', '', 0.0, 0.0,162 0, False, '', '', '', '', '', '', '', 0.0, 0.0,
165 ],163 ],
@@ -209,8 +207,7 @@
209 'twitter', 88, '240558470661799936',207 'twitter', 88, '240558470661799936',
210 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', True,208 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', True,
211 '2012-08-28T21:16:23Z', 'just another test',209 '2012-08-28T21:16:23Z', 'just another test',
212 GLib.get_user_cache_dir() +210 'https://si0.twimg.com/profile_images/730275945/oauth-dancer.jpg',
213 '/friends/avatars/ded4ba3c00583ee511f399d0b2537731ca14c39d',
214 'https://twitter.com/oauth_dancer/status/240558470661799936',211 'https://twitter.com/oauth_dancer/status/240558470661799936',
215 0, False, '', '', '', '', '', '', '', 0.0, 0.0,212 0, False, '', '', '', '', '', '', '', 0.0, 0.0,
216 ]213 ]
@@ -295,9 +292,7 @@
295 'direct_messages/sent.json?count=50')292 'direct_messages/sent.json?count=50')
296 ])293 ])
297294
298 @mock.patch('friends.protocols.twitter.Avatar.get_image',295 def test_private_avatars(self):
299 return_value='~/.cache/friends/avatars/hash')
300 def test_private_avatars(self, image_mock):
301 get_url = self.protocol._get_url = mock.Mock(296 get_url = self.protocol._get_url = mock.Mock(
302 return_value=[297 return_value=[
303 dict(298 dict(
@@ -317,7 +312,7 @@
317 publish.assert_called_with(312 publish.assert_called_with(
318 liked=False, sender='Bob', stream='private',313 liked=False, sender='Bob', stream='private',
319 url='https://twitter.com/some_guy/status/1452456',314 url='https://twitter.com/some_guy/status/1452456',
320 icon_uri='~/.cache/friends/avatars/hash',315 icon_uri='https://example.com/bob.jpg',
321 sender_nick='some_guy', sender_id='', from_me=False,316 sender_nick='some_guy', sender_id='', from_me=False,
322 timestamp='2012-11-04T17:14:52Z', message='Does my avatar show up?',317 timestamp='2012-11-04T17:14:52Z', message='Does my avatar show up?',
323 message_id='1452456')318 message_id='1452456')
@@ -474,8 +469,8 @@
474 '2013-04-16T17:58:26Z', 'RT @tarek_ziade: Just found a "Notification '469 '2013-04-16T17:58:26Z', 'RT @tarek_ziade: Just found a "Notification '
475 'of Inspection" card in the bottom of my bag. looks like they were '470 'of Inspection" card in the bottom of my bag. looks like they were '
476 'curious about those raspbe ...',471 'curious about those raspbe ...',
477 GLib.get_user_cache_dir() +472 'https://si0.twimg.com/profile_images/2631306428/'
478 '/friends/avatars/1444c8a86dbbe7a6f5f89a8135e770824d7b22bc',473 '2a509db8a05b4310394b832d34a137a4.png',
479 'https://twitter.com/therealrobru/status/324220250889543682',474 'https://twitter.com/therealrobru/status/324220250889543682',
480 0, False, '', '', '', '', '', '', '', 0.0, 0.0,475 0, False, '', '', '', '', '', '', '', 0.0, 0.0,
481 ]476 ]
482477
=== modified file 'friends/utils/avatar.py'
--- friends/utils/avatar.py 2013-04-03 21:22:57 +0000
+++ friends/utils/avatar.py 2013-06-18 22:10:33 +0000
@@ -23,17 +23,15 @@
23import os23import os
24import logging24import logging
2525
26from datetime import date, timedelta
27from gi.repository import Gio, GLib, GdkPixbuf26from gi.repository import Gio, GLib, GdkPixbuf
27from tempfile import gettempdir
28from hashlib import sha128from hashlib import sha1
2929
30from friends.utils.http import Downloader30from friends.utils.http import Downloader
31from friends.errors import ignored31from friends.errors import ignored
3232
3333
34CACHE_DIR = os.path.realpath(os.path.join(34CACHE_DIR = os.path.join(gettempdir(), 'friends-avatars')
35 GLib.get_user_cache_dir(), 'friends', 'avatars'))
36AGE_LIMIT = date.today() - timedelta(weeks=4)
3735
3836
39with ignored(FileExistsError):37with ignored(FileExistsError):
@@ -54,14 +52,11 @@
54 return url52 return url
55 local_path = Avatar.get_path(url)53 local_path = Avatar.get_path(url)
56 size = 054 size = 0
57 mtime = date.fromtimestamp(0)
5855
59 with ignored(FileNotFoundError):56 with ignored(FileNotFoundError):
60 stat = os.stat(local_path)57 size = os.stat(local_path).st_size
61 size = stat.st_size
62 mtime = date.fromtimestamp(stat.st_mtime)
6358
64 if size == 0 or mtime < AGE_LIMIT:59 if size == 0:
65 log.debug('Getting: {}'.format(url))60 log.debug('Getting: {}'.format(url))
66 image_data = Downloader(url).get_bytes()61 image_data = Downloader(url).get_bytes()
6762
@@ -79,18 +74,3 @@
79 except GLib.GError:74 except GLib.GError:
80 log.error('Failed to scale image: {}'.format(url))75 log.error('Failed to scale image: {}'.format(url))
81 return local_path76 return local_path
82
83 @staticmethod
84 def expire_old_avatars():
85 """Evict old files from the cache."""
86 log.debug('Checking if anything needs to expire.')
87 for filename in os.listdir(CACHE_DIR):
88 path = os.path.join(CACHE_DIR, filename)
89 mtime = date.fromtimestamp(os.stat(path).st_mtime)
90 if mtime < AGE_LIMIT:
91 # The file's last modification time is earlier than the oldest
92 # time we'll allow in the cache. However, due to race
93 # conditions, ignore it if the file has already been removed.
94 with ignored(FileNotFoundError):
95 log.debug('Expiring: {}'.format(path))
96 os.remove(path)
9777
=== modified file 'friends/utils/notify.py'
--- friends/utils/notify.py 2013-04-17 06:13:49 +0000
+++ friends/utils/notify.py 2013-06-18 22:10:33 +0000
@@ -27,6 +27,7 @@
2727
28from gi.repository import GObject, GdkPixbuf28from gi.repository import GObject, GdkPixbuf
2929
30from friends.utils.avatar import Avatar
30from friends.errors import ignored31from friends.errors import ignored
3132
3233
@@ -45,7 +46,7 @@
4546
46 with ignored(GObject.GError):47 with ignored(GObject.GError):
47 pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(48 pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
48 icon_uri, 48, 48)49 Avatar.get_image(icon_uri), 48, 48)
4950
50 if pixbuf is not None:51 if pixbuf is not None:
51 notification.set_icon_from_pixbuf(pixbuf)52 notification.set_icon_from_pixbuf(pixbuf)
@@ -58,6 +59,7 @@
58 # not much we can do about that.59 # not much we can do about that.
59 notification.show()60 notification.show()
6061
62
61# Optional dependency on Notify library.63# Optional dependency on Notify library.
62try:64try:
63 from gi.repository import Notify65 from gi.repository import Notify
6466
=== modified file 'service/src/service.vala'
--- service/src/service.vala 2013-04-17 18:23:44 +0000
+++ service/src/service.vala 2013-06-18 22:10:33 +0000
@@ -21,7 +21,6 @@
21[DBus (name = "com.canonical.Friends.Dispatcher")]21[DBus (name = "com.canonical.Friends.Dispatcher")]
22private interface Dispatcher : GLib.Object {22private interface Dispatcher : GLib.Object {
23 public abstract void Refresh () throws GLib.IOError;23 public abstract void Refresh () throws GLib.IOError;
24 public abstract void ExpireAvatars () throws GLib.IOError;
25 public abstract async void Do (24 public abstract async void Do (
26 string action,25 string action,
27 string account_id,26 string account_id,
@@ -184,7 +183,6 @@
184 try {183 try {
185 dispatcher = Bus.get_proxy.end(res);184 dispatcher = Bus.get_proxy.end(res);
186 Timeout.add_seconds (120, fetch_contacts);185 Timeout.add_seconds (120, fetch_contacts);
187 Timeout.add_seconds (300, expire_avatars);
188 var ret = on_refresh ();186 var ret = on_refresh ();
189 } catch (IOError e) {187 } catch (IOError e) {
190 warning (e.message);188 warning (e.message);
@@ -218,20 +216,6 @@
218 }216 }
219 return false;217 return false;
220 }218 }
221
222 bool expire_avatars ()
223 {
224 debug ("Expiring old avatars...");
225 // By default, this happens 5 minutes after startup, and then
226 // every 7 days thereafter.
227 Timeout.add_seconds (604800, expire_avatars);
228 try {
229 dispatcher.ExpireAvatars ();
230 } catch (IOError e) {
231 warning ("Failed to expire avatars - %s", e.message);
232 }
233 return false;
234 }
235}219}
236220
237void on_bus_aquired (DBusConnection conn) {221void on_bus_aquired (DBusConnection conn) {

Subscribers

People subscribed via source and target branches