Merge lp:~robru/friends/avatar-cache into lp:friends
- avatar-cache
- Merge into trunk
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 | ||||
Related bugs: |
|
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/
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.
Oh, and of course, run 'make check' ;-)
PS Jenkins bot (ps-jenkins) wrote : | # |
Ken VanDine (ken-vandine) wrote : | # |
Looks great and works well, thanks!
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'friends/protocols/facebook.py' | |||
2 | --- friends/protocols/facebook.py 2013-06-17 21:01:36 +0000 | |||
3 | +++ friends/protocols/facebook.py 2013-06-18 22:10:33 +0000 | |||
4 | @@ -24,7 +24,6 @@ | |||
5 | 24 | import time | 24 | import time |
6 | 25 | import logging | 25 | import logging |
7 | 26 | 26 | ||
8 | 27 | from friends.utils.avatar import Avatar | ||
9 | 28 | from friends.utils.base import Base, feature | 27 | from friends.utils.base import Base, feature |
10 | 29 | from friends.utils.cache import JsonCache | 28 | from friends.utils.cache import JsonCache |
11 | 30 | from friends.utils.http import Downloader, Uploader | 29 | from friends.utils.http import Downloader, Uploader |
12 | @@ -99,8 +98,8 @@ | |||
13 | 99 | args['sender_id'] = sender_id = from_record.get('id', '') | 98 | args['sender_id'] = sender_id = from_record.get('id', '') |
14 | 100 | args['url'] = STORY_PERMALINK.format( | 99 | args['url'] = STORY_PERMALINK.format( |
15 | 101 | id=sender_id, post_id=post_id) | 100 | id=sender_id, post_id=post_id) |
18 | 102 | args['icon_uri'] = Avatar.get_image( | 101 | args['icon_uri'] = (API_BASE.format(id=sender_id) + |
19 | 103 | API_BASE.format(id=sender_id) + '/picture?width=840&height=840') | 102 | '/picture?width=840&height=840') |
20 | 104 | args['sender_nick'] = from_record.get('name', '') | 103 | args['sender_nick'] = from_record.get('name', '') |
21 | 105 | args['from_me'] = (sender_id == self._account.user_id) | 104 | args['from_me'] = (sender_id == self._account.user_id) |
22 | 106 | 105 | ||
23 | @@ -322,9 +321,8 @@ | |||
24 | 322 | sender_nick=self._account.user_name, | 321 | sender_nick=self._account.user_name, |
25 | 323 | timestamp=iso8601utc(int(time.time())), | 322 | timestamp=iso8601utc(int(time.time())), |
26 | 324 | url=destination_url, | 323 | url=destination_url, |
30 | 325 | icon_uri=Avatar.get_image( | 324 | icon_uri=(API_BASE.format(id=self._account.user_id) + |
31 | 326 | API_BASE.format(id=self._account.user_id) + | 325 | '/picture?type=large')) |
29 | 327 | '/picture?type=large')) | ||
32 | 328 | return destination_url | 326 | return destination_url |
33 | 329 | else: | 327 | else: |
34 | 330 | raise FriendsError(str(response)) | 328 | raise FriendsError(str(response)) |
35 | 331 | 329 | ||
36 | === modified file 'friends/protocols/flickr.py' | |||
37 | --- friends/protocols/flickr.py 2013-03-25 23:37:08 +0000 | |||
38 | +++ friends/protocols/flickr.py 2013-06-18 22:10:33 +0000 | |||
39 | @@ -25,7 +25,6 @@ | |||
40 | 25 | import time | 25 | import time |
41 | 26 | import logging | 26 | import logging |
42 | 27 | 27 | ||
43 | 28 | from friends.utils.avatar import Avatar | ||
44 | 29 | from friends.utils.base import Base, feature | 28 | from friends.utils.base import Base, feature |
45 | 30 | from friends.utils.http import Downloader, Uploader | 29 | from friends.utils.http import Downloader, Uploader |
46 | 31 | from friends.utils.time import iso8601utc, parsetime | 30 | from friends.utils.time import iso8601utc, parsetime |
47 | @@ -93,13 +92,11 @@ | |||
48 | 93 | iconfarm = person.get('iconfarm') | 92 | iconfarm = person.get('iconfarm') |
49 | 94 | iconserver = person.get('iconserver') | 93 | iconserver = person.get('iconserver') |
50 | 95 | if None in (iconfarm, iconserver): | 94 | if None in (iconfarm, iconserver): |
54 | 96 | return Avatar.get_image( | 95 | return 'http://www.flickr.com/images/buddyicon.gif' |
55 | 97 | 'http://www.flickr.com/images/buddyicon.gif') | 96 | return BUDDY_ICON_URL.format( |
53 | 98 | avatar = BUDDY_ICON_URL.format( | ||
56 | 99 | farm=iconfarm, | 97 | farm=iconfarm, |
57 | 100 | server=iconserver, | 98 | server=iconserver, |
58 | 101 | nsid=nsid) | 99 | nsid=nsid) |
59 | 102 | return Avatar.get_image(avatar) | ||
60 | 103 | 100 | ||
61 | 104 | # http://www.flickr.com/services/api/flickr.photos.getContactsPhotos.html | 101 | # http://www.flickr.com/services/api/flickr.photos.getContactsPhotos.html |
62 | 105 | @feature | 102 | @feature |
63 | @@ -135,8 +132,8 @@ | |||
64 | 135 | url = '' | 132 | url = '' |
65 | 136 | from_me = (ownername == username) | 133 | from_me = (ownername == username) |
66 | 137 | if None not in (icon_farm, icon_server, owner): | 134 | if None not in (icon_farm, icon_server, owner): |
69 | 138 | icon_uri = Avatar.get_image(BUDDY_ICON_URL.format( | 135 | icon_uri = BUDDY_ICON_URL.format( |
70 | 139 | farm=icon_farm, server=icon_server, nsid=owner)) | 136 | farm=icon_farm, server=icon_server, nsid=owner) |
71 | 140 | url = IMAGE_PAGE_URL.format(owner=owner, photo=photo_id) | 137 | url = IMAGE_PAGE_URL.format(owner=owner, photo=photo_id) |
72 | 141 | 138 | ||
73 | 142 | # Calculate the ISO 8601 UTC time string. | 139 | # Calculate the ISO 8601 UTC time string. |
74 | 143 | 140 | ||
75 | === modified file 'friends/protocols/foursquare.py' | |||
76 | --- friends/protocols/foursquare.py 2013-03-12 04:04:07 +0000 | |||
77 | +++ friends/protocols/foursquare.py 2013-06-18 22:10:33 +0000 | |||
78 | @@ -22,7 +22,6 @@ | |||
79 | 22 | 22 | ||
80 | 23 | import logging | 23 | import logging |
81 | 24 | 24 | ||
82 | 25 | from friends.utils.avatar import Avatar | ||
83 | 26 | from friends.utils.base import Base, feature | 25 | from friends.utils.base import Base, feature |
84 | 27 | from friends.utils.http import Downloader | 26 | from friends.utils.http import Downloader |
85 | 28 | from friends.utils.time import iso8601utc | 27 | from friends.utils.time import iso8601utc |
86 | @@ -81,7 +80,6 @@ | |||
87 | 81 | for checkin in checkins: | 80 | for checkin in checkins: |
88 | 82 | user = checkin.get('user', {}) | 81 | user = checkin.get('user', {}) |
89 | 83 | avatar = user.get('photo', {}) | 82 | avatar = user.get('photo', {}) |
90 | 84 | avatar_url = '{prefix}100x100{suffix}'.format(**avatar) | ||
91 | 85 | checkin_id = checkin.get('id', '') | 83 | checkin_id = checkin.get('id', '') |
92 | 86 | tz_offset = checkin.get('timeZoneOffset', 0) | 84 | tz_offset = checkin.get('timeZoneOffset', 0) |
93 | 87 | epoch = checkin.get('createdAt', 0) | 85 | epoch = checkin.get('createdAt', 0) |
94 | @@ -95,7 +93,7 @@ | |||
95 | 95 | timestamp=iso8601utc(epoch, tz_offset), | 93 | timestamp=iso8601utc(epoch, tz_offset), |
96 | 96 | message=checkin.get('shout', ''), | 94 | message=checkin.get('shout', ''), |
97 | 97 | likes=checkin.get('likes', {}).get('count', 0), | 95 | likes=checkin.get('likes', {}).get('count', 0), |
99 | 98 | icon_uri=Avatar.get_image(avatar_url), | 96 | icon_uri='{prefix}100x100{suffix}'.format(**avatar), |
100 | 99 | url=venue.get('canonicalUrl', ''), | 97 | url=venue.get('canonicalUrl', ''), |
101 | 100 | location=venue.get('name', ''), | 98 | location=venue.get('name', ''), |
102 | 101 | latitude=location.get('lat', 0.0), | 99 | latitude=location.get('lat', 0.0), |
103 | 102 | 100 | ||
104 | === modified file 'friends/protocols/twitter.py' | |||
105 | --- friends/protocols/twitter.py 2013-04-17 06:13:49 +0000 | |||
106 | +++ friends/protocols/twitter.py 2013-06-18 22:10:33 +0000 | |||
107 | @@ -27,7 +27,6 @@ | |||
108 | 27 | 27 | ||
109 | 28 | from urllib.parse import quote | 28 | from urllib.parse import quote |
110 | 29 | 29 | ||
111 | 30 | from friends.utils.avatar import Avatar | ||
112 | 31 | from friends.utils.base import Base, feature | 30 | from friends.utils.base import Base, feature |
113 | 32 | from friends.utils.cache import JsonCache | 31 | from friends.utils.cache import JsonCache |
114 | 33 | from friends.utils.http import BaseRateLimiter, Downloader | 32 | from friends.utils.http import BaseRateLimiter, Downloader |
115 | @@ -142,8 +141,7 @@ | |||
116 | 142 | sender_id=str(user.get('id', '')), | 141 | sender_id=str(user.get('id', '')), |
117 | 143 | sender_nick=screen_name, | 142 | sender_nick=screen_name, |
118 | 144 | from_me=(screen_name == self._account.user_name), | 143 | from_me=(screen_name == self._account.user_name), |
121 | 145 | icon_uri=Avatar.get_image( | 144 | icon_uri=avatar_url.replace('_normal.', '.'), |
120 | 146 | avatar_url.replace('_normal.', '.')), | ||
122 | 147 | liked=tweet.get('favorited', False), | 145 | liked=tweet.get('favorited', False), |
123 | 148 | url=permalink, | 146 | url=permalink, |
124 | 149 | ) | 147 | ) |
125 | 150 | 148 | ||
126 | === modified file 'friends/service/dispatcher.py' | |||
127 | --- friends/service/dispatcher.py 2013-04-02 21:54:24 +0000 | |||
128 | +++ friends/service/dispatcher.py 2013-06-18 22:10:33 +0000 | |||
129 | @@ -30,7 +30,6 @@ | |||
130 | 30 | from gi.repository import GLib | 30 | from gi.repository import GLib |
131 | 31 | from contextlib import ContextDecorator | 31 | from contextlib import ContextDecorator |
132 | 32 | 32 | ||
133 | 33 | from friends.utils.avatar import Avatar | ||
134 | 34 | from friends.utils.account import find_accounts | 33 | from friends.utils.account import find_accounts |
135 | 35 | from friends.utils.manager import protocol_manager | 34 | from friends.utils.manager import protocol_manager |
136 | 36 | from friends.utils.menus import MenuManager | 35 | from friends.utils.menus import MenuManager |
137 | @@ -347,11 +346,3 @@ | |||
138 | 347 | if not self.settings.get_boolean('shorten-urls'): | 346 | if not self.settings.get_boolean('shorten-urls'): |
139 | 348 | return message | 347 | return message |
140 | 349 | return Short(service_name).sub(message) | 348 | return Short(service_name).sub(message) |
141 | 350 | |||
142 | 351 | @exit_after_idle | ||
143 | 352 | @dbus.service.method(DBUS_INTERFACE) | ||
144 | 353 | def ExpireAvatars(self): | ||
145 | 354 | pass | ||
146 | 355 | # Disabled because we don't currently have a way of re-fetching | ||
147 | 356 | # expired avatars if they're needed again later. | ||
148 | 357 | # Avatar.expire_old_avatars() | ||
149 | 358 | 349 | ||
150 | === modified file 'friends/service/mock_service.py' | |||
151 | --- friends/service/mock_service.py 2013-03-13 04:36:33 +0000 | |||
152 | +++ friends/service/mock_service.py 2013-06-18 22:10:33 +0000 | |||
153 | @@ -95,7 +95,3 @@ | |||
154 | 95 | @dbus.service.method(DBUS_INTERFACE, in_signature='s', out_signature='s') | 95 | @dbus.service.method(DBUS_INTERFACE, in_signature='s', out_signature='s') |
155 | 96 | def URLShorten(self, url): | 96 | def URLShorten(self, url): |
156 | 97 | return str(len(url)) | 97 | return str(len(url)) |
157 | 98 | |||
158 | 99 | @dbus.service.method(DBUS_INTERFACE) | ||
159 | 100 | def ExpireAvatars(self): | ||
160 | 101 | pass | ||
161 | 102 | 98 | ||
162 | === modified file 'friends/tests/test_avatars.py' | |||
163 | --- friends/tests/test_avatars.py 2013-02-28 19:10:20 +0000 | |||
164 | +++ friends/tests/test_avatars.py 2013-06-18 22:10:33 +0000 | |||
165 | @@ -130,32 +130,3 @@ | |||
166 | 130 | # This is the PNG file format magic number, living in the first 8 | 130 | # This is the PNG file format magic number, living in the first 8 |
167 | 131 | # bytes of the file. | 131 | # bytes of the file. |
168 | 132 | self.assertEqual(raw.read(8), bytes.fromhex('89504E470D0A1A0A')) | 132 | self.assertEqual(raw.read(8), bytes.fromhex('89504E470D0A1A0A')) |
169 | 133 | |||
170 | 134 | def test_cache_expiration(self): | ||
171 | 135 | # Cache files which are more than 4 weeks old get expired. | ||
172 | 136 | # | ||
173 | 137 | # Start by copying two copies of ubuntu.png to the temporary cache | ||
174 | 138 | # dir. Fiddle with their mtimes. so that one is just younger than 4 | ||
175 | 139 | # weeks old and one is just older than 4 weeks old. Run the cache | ||
176 | 140 | # eviction method and ensure that the young one is retained while the | ||
177 | 141 | # old one is removed. | ||
178 | 142 | with mock.patch('friends.utils.avatar.CACHE_DIR', | ||
179 | 143 | self._avatar_cache) as cache_dir: | ||
180 | 144 | os.makedirs(cache_dir) | ||
181 | 145 | src = resource_filename('friends.tests.data', 'ubuntu.png') | ||
182 | 146 | aaa = os.path.join(cache_dir, 'aaa') | ||
183 | 147 | shutil.copyfile(src, aaa) | ||
184 | 148 | bbb = os.path.join(cache_dir, 'bbb') | ||
185 | 149 | shutil.copyfile(src, bbb) | ||
186 | 150 | # Leave the atime unchanged. | ||
187 | 151 | four_weeks_ago = date.today() - timedelta(weeks=4) | ||
188 | 152 | young = four_weeks_ago + timedelta(days=1) | ||
189 | 153 | old = four_weeks_ago - timedelta(days=1) | ||
190 | 154 | # aaa will be young enough to keep (i.e. 4 weeks less one day ago) | ||
191 | 155 | os.utime(aaa, | ||
192 | 156 | (os.stat(aaa).st_atime, time.mktime(young.timetuple()))) | ||
193 | 157 | # bbb will be too old to keep (i.e. 4 weeks plus one day ago) | ||
194 | 158 | os.utime(bbb, | ||
195 | 159 | (os.stat(bbb).st_atime, time.mktime(old.timetuple()))) | ||
196 | 160 | Avatar.expire_old_avatars() | ||
197 | 161 | self.assertEqual(os.listdir(cache_dir), ['aaa']) | ||
198 | 162 | 133 | ||
199 | === modified file 'friends/tests/test_facebook.py' | |||
200 | --- friends/tests/test_facebook.py 2013-06-11 19:58:29 +0000 | |||
201 | +++ friends/tests/test_facebook.py 2013-06-18 22:10:33 +0000 | |||
202 | @@ -129,8 +129,7 @@ | |||
203 | 129 | 'Writing code that supports geotagging data from facebook. ' + | 129 | 'Writing code that supports geotagging data from facebook. ' + |
204 | 130 | 'If y\'all could make some geotagged facebook posts for me ' + | 130 | 'If y\'all could make some geotagged facebook posts for me ' + |
205 | 131 | 'to test with, that\'d be super.', | 131 | 'to test with, that\'d be super.', |
208 | 132 | GLib.get_user_cache_dir() + | 132 | 'https://graph.facebook.com/56789/picture?width=840&height=840', |
207 | 133 | '/friends/avatars/5c4e74c64b1a09343558afc1046c2b1d176a2ba2', | ||
209 | 134 | 'https://www.facebook.com/56789/posts/postid1', | 133 | 'https://www.facebook.com/56789/posts/postid1', |
210 | 135 | 1, | 134 | 1, |
211 | 136 | False, | 135 | False, |
212 | @@ -155,8 +154,7 @@ | |||
213 | 155 | False, | 154 | False, |
214 | 156 | '2013-03-12T23:29:45Z', | 155 | '2013-03-12T23:29:45Z', |
215 | 157 | 'don\'t know how', | 156 | 'don\'t know how', |
218 | 158 | GLib.get_user_cache_dir() + | 157 | 'https://graph.facebook.com/234/picture?width=840&height=840', |
217 | 159 | '/friends/avatars/9b9379ccc7948e4804dff7914bfa4c6de3974df5', | ||
219 | 160 | 'https://www.facebook.com/234/posts/commentid2', | 158 | 'https://www.facebook.com/234/posts/commentid2', |
220 | 161 | 0, | 159 | 0, |
221 | 162 | False, | 160 | False, |
222 | @@ -186,8 +184,7 @@ | |||
223 | 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 ' + |
224 | 187 | 'Hannah Hart (My Drunk Kitchen) and Angry Video Game Nerd ' + | 185 | 'Hannah Hart (My Drunk Kitchen) and Angry Video Game Nerd ' + |
225 | 188 | 'producer, Sean Keegan. Stanley is in the lobby.', | 186 | 'producer, Sean Keegan. Stanley is in the lobby.', |
228 | 189 | GLib.get_user_cache_dir() + | 187 | 'https://graph.facebook.com/161247843901324/picture?width=840&height=840', |
227 | 190 | '/friends/avatars/5b2d70e788df790b9c8db4c6a138fc4a1f433ec9', | ||
229 | 191 | 'https://www.facebook.com/161247843901324/posts/629147610444676', | 188 | 'https://www.facebook.com/161247843901324/posts/629147610444676', |
230 | 192 | 84, | 189 | 84, |
231 | 193 | False, | 190 | False, |
232 | @@ -213,8 +210,7 @@ | |||
233 | 213 | False, | 210 | False, |
234 | 214 | '2013-03-15T19:57:14Z', | 211 | '2013-03-15T19:57:14Z', |
235 | 215 | 'Guy Frenchie did some things with some stuff.', | 212 | 'Guy Frenchie did some things with some stuff.', |
238 | 216 | GLib.get_user_cache_dir() + | 213 | 'https://graph.facebook.com/1244414/picture?width=840&height=840', |
237 | 217 | '/friends/avatars/3f5e276af0c43f6411d931b829123825ede1968e', | ||
239 | 218 | 'https://www.facebook.com/1244414/posts/100085049977', | 214 | 'https://www.facebook.com/1244414/posts/100085049977', |
240 | 219 | 3, | 215 | 3, |
241 | 220 | False, | 216 | False, |
242 | @@ -370,8 +366,7 @@ | |||
243 | 370 | timestamp='2012-11-06T13:49:08Z', | 366 | timestamp='2012-11-06T13:49:08Z', |
244 | 371 | sender_id=None, | 367 | sender_id=None, |
245 | 372 | from_me=True, | 368 | from_me=True, |
248 | 373 | icon_uri=GLib.get_user_cache_dir() + | 369 | icon_uri='https://graph.facebook.com/None/picture?type=large', |
247 | 374 | '/friends/avatars/d49d72a384d50adf7c736ba27ca55bfa9fa5782d', | ||
249 | 375 | message='This is Ubuntu!', | 370 | message='This is Ubuntu!', |
250 | 376 | message_id='234125', | 371 | message_id='234125', |
251 | 377 | sender=None) | 372 | sender=None) |
252 | 378 | 373 | ||
253 | === modified file 'friends/tests/test_flickr.py' | |||
254 | --- friends/tests/test_flickr.py 2013-03-25 23:37:08 +0000 | |||
255 | +++ friends/tests/test_flickr.py 2013-06-18 22:10:33 +0000 | |||
256 | @@ -22,8 +22,6 @@ | |||
257 | 22 | 22 | ||
258 | 23 | import unittest | 23 | import unittest |
259 | 24 | 24 | ||
260 | 25 | from gi.repository import GLib | ||
261 | 26 | |||
262 | 27 | from friends.errors import AuthorizationError, FriendsError | 25 | from friends.errors import AuthorizationError, FriendsError |
263 | 28 | from friends.protocols.flickr import Flickr | 26 | from friends.protocols.flickr import Flickr |
264 | 29 | from friends.tests.mocks import FakeAccount, FakeSoupMessage, LogMock | 27 | from friends.tests.mocks import FakeAccount, FakeSoupMessage, LogMock |
265 | @@ -176,8 +174,7 @@ | |||
266 | 176 | True, | 174 | True, |
267 | 177 | '2013-03-12T19:51:42Z', | 175 | '2013-03-12T19:51:42Z', |
268 | 178 | 'Chocolate chai #yegcoffee', | 176 | 'Chocolate chai #yegcoffee', |
271 | 179 | GLib.get_user_cache_dir() + | 177 | 'http://farm1.static.flickr.com/93/buddyicons/47303164@N00.jpg', |
270 | 180 | '/friends/avatars/7b30ff0140dd9b80f2b1782a2802c3ce785fa0ce', | ||
272 | 181 | 'http://www.flickr.com/photos/47303164@N00/8552892154', | 178 | 'http://www.flickr.com/photos/47303164@N00/8552892154', |
273 | 182 | 0, | 179 | 0, |
274 | 183 | False, | 180 | False, |
275 | @@ -204,8 +201,7 @@ | |||
276 | 204 | True, | 201 | True, |
277 | 205 | '2013-03-12T13:54:10Z', | 202 | '2013-03-12T13:54:10Z', |
278 | 206 | 'St. Michael - The Archangel', | 203 | 'St. Michael - The Archangel', |
281 | 207 | GLib.get_user_cache_dir() + | 204 | 'http://farm3.static.flickr.com/2047/buddyicons/27204141@N05.jpg', |
280 | 208 | '/friends/avatars/cae2939354a33fea5f008df91bb8e25920be5dc3', | ||
282 | 209 | 'http://www.flickr.com/photos/27204141@N05/8550829193', | 205 | 'http://www.flickr.com/photos/27204141@N05/8550829193', |
283 | 210 | 0, | 206 | 0, |
284 | 211 | False, | 207 | False, |
285 | 212 | 208 | ||
286 | === modified file 'friends/tests/test_foursquare.py' | |||
287 | --- friends/tests/test_foursquare.py 2013-03-21 21:49:26 +0000 | |||
288 | +++ friends/tests/test_foursquare.py 2013-06-18 22:10:33 +0000 | |||
289 | @@ -83,8 +83,6 @@ | |||
290 | 83 | FakeSoupMessage('friends.tests.data', 'foursquare-full.dat')) | 83 | FakeSoupMessage('friends.tests.data', 'foursquare-full.dat')) |
291 | 84 | @mock.patch('friends.protocols.foursquare.FourSquare._login', | 84 | @mock.patch('friends.protocols.foursquare.FourSquare._login', |
292 | 85 | return_value=True) | 85 | return_value=True) |
293 | 86 | @mock.patch('friends.protocols.foursquare.Avatar.get_image', | ||
294 | 87 | return_value='~/.cache/friends/avatar/hash') | ||
295 | 88 | def test_receive(self, *mocks): | 86 | def test_receive(self, *mocks): |
296 | 89 | self.account.access_token = 'tokeny goodness' | 87 | self.account.access_token = 'tokeny goodness' |
297 | 90 | self.assertEqual(0, TestModel.get_n_rows()) | 88 | self.assertEqual(0, TestModel.get_n_rows()) |
298 | @@ -94,8 +92,9 @@ | |||
299 | 94 | 'foursquare', 88, '50574c9ce4b0a9a6e84433a0', | 92 | 'foursquare', 88, '50574c9ce4b0a9a6e84433a0', |
300 | 95 | 'messages', 'Jimbob Smith', '', '', True, '2012-09-17T19:15:24Z', | 93 | 'messages', 'Jimbob Smith', '', '', True, '2012-09-17T19:15:24Z', |
301 | 96 | "Working on friends's foursquare plugin.", | 94 | "Working on friends's foursquare plugin.", |
304 | 97 | '~/.cache/friends/avatar/hash', '', 0, False, '', '', '', | 95 | 'https://irs0.4sqi.net/img/user/100x100/5IEW3VIX55BBEXAO.jpg', |
305 | 98 | '', '', '', 'Pop Soda\'s Coffee House & Gallery', | 96 | '', 0, False, '', '', '', '', '', '', |
306 | 97 | 'Pop Soda\'s Coffee House & Gallery', | ||
307 | 99 | 49.88873164336725, -97.158043384552, | 98 | 49.88873164336725, -97.158043384552, |
308 | 100 | ] | 99 | ] |
309 | 101 | self.assertEqual(list(TestModel.get_row(0)), expected) | 100 | self.assertEqual(list(TestModel.get_row(0)), expected) |
310 | 102 | 101 | ||
311 | === modified file 'friends/tests/test_notify.py' | |||
312 | --- friends/tests/test_notify.py 2013-04-16 17:55:01 +0000 | |||
313 | +++ friends/tests/test_notify.py 2013-06-18 22:10:33 +0000 | |||
314 | @@ -40,6 +40,25 @@ | |||
315 | 40 | 40 | ||
316 | 41 | @mock.patch('friends.utils.base.Model', TestModel) | 41 | @mock.patch('friends.utils.base.Model', TestModel) |
317 | 42 | @mock.patch('friends.utils.base._seen_ids', {}) | 42 | @mock.patch('friends.utils.base._seen_ids', {}) |
318 | 43 | @mock.patch('friends.utils.notify.Avatar') | ||
319 | 44 | @mock.patch('friends.utils.notify.GdkPixbuf') | ||
320 | 45 | @mock.patch('friends.utils.notify.Notify') | ||
321 | 46 | def test_publish_avatar_cache(self, notify, gdkpixbuf, avatar): | ||
322 | 47 | Base._do_notify = lambda protocol, stream: True | ||
323 | 48 | base = Base(FakeAccount()) | ||
324 | 49 | base._publish( | ||
325 | 50 | message='http://example.com!', | ||
326 | 51 | message_id='1234', | ||
327 | 52 | sender='Benjamin', | ||
328 | 53 | timestamp=RIGHT_NOW, | ||
329 | 54 | icon_uri='http://example.com/bob.jpg', | ||
330 | 55 | ) | ||
331 | 56 | avatar.get_image.assert_called_once_with('http://example.com/bob.jpg') | ||
332 | 57 | gdkpixbuf.Pixbuf.new_from_file_at_size.assert_called_once_with( | ||
333 | 58 | avatar.get_image(), 48, 48) | ||
334 | 59 | |||
335 | 60 | @mock.patch('friends.utils.base.Model', TestModel) | ||
336 | 61 | @mock.patch('friends.utils.base._seen_ids', {}) | ||
337 | 43 | @mock.patch('friends.utils.base.notify') | 62 | @mock.patch('friends.utils.base.notify') |
338 | 44 | def test_publish_no_html(self, notify): | 63 | def test_publish_no_html(self, notify): |
339 | 45 | Base._do_notify = lambda protocol, stream: True | 64 | Base._do_notify = lambda protocol, stream: True |
340 | 46 | 65 | ||
341 | === modified file 'friends/tests/test_twitter.py' | |||
342 | --- friends/tests/test_twitter.py 2013-04-17 06:13:49 +0000 | |||
343 | +++ friends/tests/test_twitter.py 2013-06-18 22:10:33 +0000 | |||
344 | @@ -26,7 +26,6 @@ | |||
345 | 26 | import unittest | 26 | import unittest |
346 | 27 | import shutil | 27 | import shutil |
347 | 28 | 28 | ||
348 | 29 | from gi.repository import GLib | ||
349 | 30 | from urllib.error import HTTPError | 29 | from urllib.error import HTTPError |
350 | 31 | 30 | ||
351 | 32 | from friends.protocols.twitter import RateLimiter, Twitter | 31 | from friends.protocols.twitter import RateLimiter, Twitter |
352 | @@ -138,8 +137,7 @@ | |||
353 | 138 | ['twitter', 88, '240558470661799936', | 137 | ['twitter', 88, '240558470661799936', |
354 | 139 | 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', False, | 138 | 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', False, |
355 | 140 | '2012-08-28T21:16:23Z', 'just another test', | 139 | '2012-08-28T21:16:23Z', 'just another test', |
358 | 141 | GLib.get_user_cache_dir() + | 140 | 'https://si0.twimg.com/profile_images/730275945/oauth-dancer.jpg', |
357 | 142 | '/friends/avatars/ded4ba3c00583ee511f399d0b2537731ca14c39d', | ||
359 | 143 | 'https://twitter.com/oauth_dancer/status/240558470661799936', | 141 | 'https://twitter.com/oauth_dancer/status/240558470661799936', |
360 | 144 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, | 142 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, |
361 | 145 | ], | 143 | ], |
362 | @@ -149,8 +147,8 @@ | |||
363 | 149 | 'with twitter" class at @cal with @othman ' | 147 | 'with twitter" class at @cal with @othman ' |
364 | 150 | '<a href="http://blogs.ischool.berkeley.edu/i290-abdt-s12/">' | 148 | '<a href="http://blogs.ischool.berkeley.edu/i290-abdt-s12/">' |
365 | 151 | 'http://blogs.ischool.berkeley.edu/i290-abdt-s12/</a>', | 149 | 'http://blogs.ischool.berkeley.edu/i290-abdt-s12/</a>', |
368 | 152 | GLib.get_user_cache_dir() + | 150 | 'https://si0.twimg.com/profile_images/1270234259/' |
369 | 153 | '/friends/avatars/0219effc03a3049a622476e6e001a4014f33dc31', | 151 | 'raffi-headshot-casual.png', |
370 | 154 | 'https://twitter.com/raffi/status/240556426106372096', | 152 | 'https://twitter.com/raffi/status/240556426106372096', |
371 | 155 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, | 153 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, |
372 | 156 | ], | 154 | ], |
373 | @@ -158,8 +156,8 @@ | |||
374 | 158 | 'messages', 'Taylor Singletary', '819797', 'episod', False, | 156 | 'messages', 'Taylor Singletary', '819797', 'episod', False, |
375 | 159 | '2012-08-28T19:59:34Z', | 157 | '2012-08-28T19:59:34Z', |
376 | 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.', |
379 | 161 | GLib.get_user_cache_dir() + | 159 | 'https://si0.twimg.com/profile_images/2546730059/' |
380 | 162 | '/friends/avatars/0c829cb2934ad76489be21ee5e103735d9b7b034', | 160 | 'f6a8zq58mg1hn0ha8vie.jpeg', |
381 | 163 | 'https://twitter.com/episod/status/240539141056638977', | 161 | 'https://twitter.com/episod/status/240539141056638977', |
382 | 164 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, | 162 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, |
383 | 165 | ], | 163 | ], |
384 | @@ -209,8 +207,7 @@ | |||
385 | 209 | 'twitter', 88, '240558470661799936', | 207 | 'twitter', 88, '240558470661799936', |
386 | 210 | 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', True, | 208 | 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', True, |
387 | 211 | '2012-08-28T21:16:23Z', 'just another test', | 209 | '2012-08-28T21:16:23Z', 'just another test', |
390 | 212 | GLib.get_user_cache_dir() + | 210 | 'https://si0.twimg.com/profile_images/730275945/oauth-dancer.jpg', |
389 | 213 | '/friends/avatars/ded4ba3c00583ee511f399d0b2537731ca14c39d', | ||
391 | 214 | 'https://twitter.com/oauth_dancer/status/240558470661799936', | 211 | 'https://twitter.com/oauth_dancer/status/240558470661799936', |
392 | 215 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, | 212 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, |
393 | 216 | ] | 213 | ] |
394 | @@ -295,9 +292,7 @@ | |||
395 | 295 | 'direct_messages/sent.json?count=50') | 292 | 'direct_messages/sent.json?count=50') |
396 | 296 | ]) | 293 | ]) |
397 | 297 | 294 | ||
401 | 298 | @mock.patch('friends.protocols.twitter.Avatar.get_image', | 295 | def test_private_avatars(self): |
399 | 299 | return_value='~/.cache/friends/avatars/hash') | ||
400 | 300 | def test_private_avatars(self, image_mock): | ||
402 | 301 | get_url = self.protocol._get_url = mock.Mock( | 296 | get_url = self.protocol._get_url = mock.Mock( |
403 | 302 | return_value=[ | 297 | return_value=[ |
404 | 303 | dict( | 298 | dict( |
405 | @@ -317,7 +312,7 @@ | |||
406 | 317 | publish.assert_called_with( | 312 | publish.assert_called_with( |
407 | 318 | liked=False, sender='Bob', stream='private', | 313 | liked=False, sender='Bob', stream='private', |
408 | 319 | url='https://twitter.com/some_guy/status/1452456', | 314 | url='https://twitter.com/some_guy/status/1452456', |
410 | 320 | icon_uri='~/.cache/friends/avatars/hash', | 315 | icon_uri='https://example.com/bob.jpg', |
411 | 321 | sender_nick='some_guy', sender_id='', from_me=False, | 316 | sender_nick='some_guy', sender_id='', from_me=False, |
412 | 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?', |
413 | 323 | message_id='1452456') | 318 | message_id='1452456') |
414 | @@ -474,8 +469,8 @@ | |||
415 | 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 ' |
416 | 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 ' |
417 | 476 | 'curious about those raspbe ...', | 471 | 'curious about those raspbe ...', |
420 | 477 | GLib.get_user_cache_dir() + | 472 | 'https://si0.twimg.com/profile_images/2631306428/' |
421 | 478 | '/friends/avatars/1444c8a86dbbe7a6f5f89a8135e770824d7b22bc', | 473 | '2a509db8a05b4310394b832d34a137a4.png', |
422 | 479 | 'https://twitter.com/therealrobru/status/324220250889543682', | 474 | 'https://twitter.com/therealrobru/status/324220250889543682', |
423 | 480 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, | 475 | 0, False, '', '', '', '', '', '', '', 0.0, 0.0, |
424 | 481 | ] | 476 | ] |
425 | 482 | 477 | ||
426 | === modified file 'friends/utils/avatar.py' | |||
427 | --- friends/utils/avatar.py 2013-04-03 21:22:57 +0000 | |||
428 | +++ friends/utils/avatar.py 2013-06-18 22:10:33 +0000 | |||
429 | @@ -23,17 +23,15 @@ | |||
430 | 23 | import os | 23 | import os |
431 | 24 | import logging | 24 | import logging |
432 | 25 | 25 | ||
433 | 26 | from datetime import date, timedelta | ||
434 | 27 | from gi.repository import Gio, GLib, GdkPixbuf | 26 | from gi.repository import Gio, GLib, GdkPixbuf |
435 | 27 | from tempfile import gettempdir | ||
436 | 28 | from hashlib import sha1 | 28 | from hashlib import sha1 |
437 | 29 | 29 | ||
438 | 30 | from friends.utils.http import Downloader | 30 | from friends.utils.http import Downloader |
439 | 31 | from friends.errors import ignored | 31 | from friends.errors import ignored |
440 | 32 | 32 | ||
441 | 33 | 33 | ||
445 | 34 | CACHE_DIR = os.path.realpath(os.path.join( | 34 | CACHE_DIR = os.path.join(gettempdir(), 'friends-avatars') |
443 | 35 | GLib.get_user_cache_dir(), 'friends', 'avatars')) | ||
444 | 36 | AGE_LIMIT = date.today() - timedelta(weeks=4) | ||
446 | 37 | 35 | ||
447 | 38 | 36 | ||
448 | 39 | with ignored(FileExistsError): | 37 | with ignored(FileExistsError): |
449 | @@ -54,14 +52,11 @@ | |||
450 | 54 | return url | 52 | return url |
451 | 55 | local_path = Avatar.get_path(url) | 53 | local_path = Avatar.get_path(url) |
452 | 56 | size = 0 | 54 | size = 0 |
453 | 57 | mtime = date.fromtimestamp(0) | ||
454 | 58 | 55 | ||
455 | 59 | with ignored(FileNotFoundError): | 56 | with ignored(FileNotFoundError): |
459 | 60 | stat = os.stat(local_path) | 57 | size = os.stat(local_path).st_size |
457 | 61 | size = stat.st_size | ||
458 | 62 | mtime = date.fromtimestamp(stat.st_mtime) | ||
460 | 63 | 58 | ||
462 | 64 | if size == 0 or mtime < AGE_LIMIT: | 59 | if size == 0: |
463 | 65 | log.debug('Getting: {}'.format(url)) | 60 | log.debug('Getting: {}'.format(url)) |
464 | 66 | image_data = Downloader(url).get_bytes() | 61 | image_data = Downloader(url).get_bytes() |
465 | 67 | 62 | ||
466 | @@ -79,18 +74,3 @@ | |||
467 | 79 | except GLib.GError: | 74 | except GLib.GError: |
468 | 80 | log.error('Failed to scale image: {}'.format(url)) | 75 | log.error('Failed to scale image: {}'.format(url)) |
469 | 81 | return local_path | 76 | return local_path |
470 | 82 | |||
471 | 83 | @staticmethod | ||
472 | 84 | def expire_old_avatars(): | ||
473 | 85 | """Evict old files from the cache.""" | ||
474 | 86 | log.debug('Checking if anything needs to expire.') | ||
475 | 87 | for filename in os.listdir(CACHE_DIR): | ||
476 | 88 | path = os.path.join(CACHE_DIR, filename) | ||
477 | 89 | mtime = date.fromtimestamp(os.stat(path).st_mtime) | ||
478 | 90 | if mtime < AGE_LIMIT: | ||
479 | 91 | # The file's last modification time is earlier than the oldest | ||
480 | 92 | # time we'll allow in the cache. However, due to race | ||
481 | 93 | # conditions, ignore it if the file has already been removed. | ||
482 | 94 | with ignored(FileNotFoundError): | ||
483 | 95 | log.debug('Expiring: {}'.format(path)) | ||
484 | 96 | os.remove(path) | ||
485 | 97 | 77 | ||
486 | === modified file 'friends/utils/notify.py' | |||
487 | --- friends/utils/notify.py 2013-04-17 06:13:49 +0000 | |||
488 | +++ friends/utils/notify.py 2013-06-18 22:10:33 +0000 | |||
489 | @@ -27,6 +27,7 @@ | |||
490 | 27 | 27 | ||
491 | 28 | from gi.repository import GObject, GdkPixbuf | 28 | from gi.repository import GObject, GdkPixbuf |
492 | 29 | 29 | ||
493 | 30 | from friends.utils.avatar import Avatar | ||
494 | 30 | from friends.errors import ignored | 31 | from friends.errors import ignored |
495 | 31 | 32 | ||
496 | 32 | 33 | ||
497 | @@ -45,7 +46,7 @@ | |||
498 | 45 | 46 | ||
499 | 46 | with ignored(GObject.GError): | 47 | with ignored(GObject.GError): |
500 | 47 | pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( | 48 | pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( |
502 | 48 | icon_uri, 48, 48) | 49 | Avatar.get_image(icon_uri), 48, 48) |
503 | 49 | 50 | ||
504 | 50 | if pixbuf is not None: | 51 | if pixbuf is not None: |
505 | 51 | notification.set_icon_from_pixbuf(pixbuf) | 52 | notification.set_icon_from_pixbuf(pixbuf) |
506 | @@ -58,6 +59,7 @@ | |||
507 | 58 | # not much we can do about that. | 59 | # not much we can do about that. |
508 | 59 | notification.show() | 60 | notification.show() |
509 | 60 | 61 | ||
510 | 62 | |||
511 | 61 | # Optional dependency on Notify library. | 63 | # Optional dependency on Notify library. |
512 | 62 | try: | 64 | try: |
513 | 63 | from gi.repository import Notify | 65 | from gi.repository import Notify |
514 | 64 | 66 | ||
515 | === modified file 'service/src/service.vala' | |||
516 | --- service/src/service.vala 2013-04-17 18:23:44 +0000 | |||
517 | +++ service/src/service.vala 2013-06-18 22:10:33 +0000 | |||
518 | @@ -21,7 +21,6 @@ | |||
519 | 21 | [DBus (name = "com.canonical.Friends.Dispatcher")] | 21 | [DBus (name = "com.canonical.Friends.Dispatcher")] |
520 | 22 | private interface Dispatcher : GLib.Object { | 22 | private interface Dispatcher : GLib.Object { |
521 | 23 | public abstract void Refresh () throws GLib.IOError; | 23 | public abstract void Refresh () throws GLib.IOError; |
522 | 24 | public abstract void ExpireAvatars () throws GLib.IOError; | ||
523 | 25 | public abstract async void Do ( | 24 | public abstract async void Do ( |
524 | 26 | string action, | 25 | string action, |
525 | 27 | string account_id, | 26 | string account_id, |
526 | @@ -184,7 +183,6 @@ | |||
527 | 184 | try { | 183 | try { |
528 | 185 | dispatcher = Bus.get_proxy.end(res); | 184 | dispatcher = Bus.get_proxy.end(res); |
529 | 186 | Timeout.add_seconds (120, fetch_contacts); | 185 | Timeout.add_seconds (120, fetch_contacts); |
530 | 187 | Timeout.add_seconds (300, expire_avatars); | ||
531 | 188 | var ret = on_refresh (); | 186 | var ret = on_refresh (); |
532 | 189 | } catch (IOError e) { | 187 | } catch (IOError e) { |
533 | 190 | warning (e.message); | 188 | warning (e.message); |
534 | @@ -218,20 +216,6 @@ | |||
535 | 218 | } | 216 | } |
536 | 219 | return false; | 217 | return false; |
537 | 220 | } | 218 | } |
538 | 221 | |||
539 | 222 | bool expire_avatars () | ||
540 | 223 | { | ||
541 | 224 | debug ("Expiring old avatars..."); | ||
542 | 225 | // By default, this happens 5 minutes after startup, and then | ||
543 | 226 | // every 7 days thereafter. | ||
544 | 227 | Timeout.add_seconds (604800, expire_avatars); | ||
545 | 228 | try { | ||
546 | 229 | dispatcher.ExpireAvatars (); | ||
547 | 230 | } catch (IOError e) { | ||
548 | 231 | warning ("Failed to expire avatars - %s", e.message); | ||
549 | 232 | } | ||
550 | 233 | return false; | ||
551 | 234 | } | ||
552 | 235 | } | 219 | } |
553 | 236 | 220 | ||
554 | 237 | void on_bus_aquired (DBusConnection conn) { | 221 | void on_bus_aquired (DBusConnection conn) { |
FAILED: Continuous integration, rev:208 /code.launchpad .net/~robru/ friends/ avatar- cache/+ merge/170206/ +edit-commit- message
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:/
http:// jenkins. qa.ubuntu. com/job/ friends- ci/46/ jenkins. qa.ubuntu. com/job/ friends- saucy-amd64- ci/3
Executed test runs:
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ friends- ci/46/rebuild
http://