Merge lp:~cjwatson/ubiquity/libsoup-timezone into lp:ubiquity
- libsoup-timezone
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 5006 |
Proposed branch: | lp:~cjwatson/ubiquity/libsoup-timezone |
Merge into: | lp:ubiquity |
Diff against target: |
219 lines (+112/-42) (has conflicts) 4 files modified
debian/changelog (+10/-0) debian/control (+1/-1) tests/test_timezone.py (+18/-2) ubiquity/plugins/ubi-timezone.py (+83/-39) Text conflict in debian/changelog |
To merge this branch: | bzr merge lp:~cjwatson/ubiquity/libsoup-timezone |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Evan (community) | Needs Information | ||
Review via email: mp+77324@code.launchpad.net |
Commit message
Description of the change
Fix bug 837217 by fetching geoname data asynchronously using libsoup.
Shouldn't the block of code that does a lookup against self.zones still live in the changed() function? Otherwise, the user will only get results, including locally-computed ones, if the callback for hitting the geonames server succeeds.
(This caused the test_city_entry test to fail, which expects to get some local results upon setting the entry to Eastern.)
Colin Watson (cjwatson) wrote : | # |
Fixed the missing imports, thanks.
The callback is supposed to fire no matter what, whether by cancellation or timeout or whatever; in my tests, I got locally-computed results even when the network was dropping all outbound port-80 packets. But perhaps it would be better to set the completion model straight away and fill it in with network results once they arrive; the main gotcha there is making sure that we only cache once we get network results.
Apologies, I was indeed wrong about the callback only firing on success.
Matthew suggests that a reasonable threshold is that if the timeout is going to be greater than 2-3 seconds, we should show the locally-computed results while we wait for network results.
- 4997. By Colin Watson
-
missing imports
- 4998. By Colin Watson
-
Show tzdb completion entries after a short timeout.
- 4999. By Colin Watson
-
need to encode text before passing to urllib.quote
- 5000. By Colin Watson
-
fix up tests
Preview Diff
1 | === modified file 'debian/changelog' |
2 | --- debian/changelog 2011-09-30 13:49:36 +0000 |
3 | +++ debian/changelog 2011-09-30 21:34:24 +0000 |
4 | @@ -1,3 +1,4 @@ |
5 | +<<<<<<< TREE |
6 | ubiquity (2.8.0) UNRELEASED; urgency=low |
7 | |
8 | * Fix SSID UTF-8 decoding, and replace any still-invalid characters with |
9 | @@ -29,6 +30,15 @@ |
10 | |
11 | -- Adam Conrad <adconrad@ubuntu.com> Wed, 28 Sep 2011 16:43:42 -0600 |
12 | |
13 | +======= |
14 | +ubiquity (2.7.38) UNRELEASED; urgency=low |
15 | + |
16 | + * GTK frontend: |
17 | + - Fetch geoname data asynchronously (LP: #837217). |
18 | + |
19 | + -- Colin Watson <cjwatson@ubuntu.com> Wed, 28 Sep 2011 13:43:44 +0100 |
20 | + |
21 | +>>>>>>> MERGE-SOURCE |
22 | ubiquity (2.7.37) oneiric; urgency=low |
23 | |
24 | [ Evan Dandrea ] |
25 | |
26 | === modified file 'debian/control' |
27 | --- debian/control 2011-09-28 09:54:01 +0000 |
28 | +++ debian/control 2011-09-30 21:34:24 +0000 |
29 | @@ -43,7 +43,7 @@ |
30 | |
31 | Package: ubiquity-frontend-gtk |
32 | Architecture: any |
33 | -Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, ubiquity (= ${binary:Version}), python-dbus, gir1.2-gtk-3.0, gir1.2-gdkpixbuf-2.0, gir1.2-gstreamer-0.10, gir1.2-vte-2.90, gir1.2-webkit-3.0, iso-codes, x-window-manager, gksu, python-xklavier, gir1.2-timezonemap-1.0, python-libxml2, python-cairo, python-appindicator, gstreamer0.10-plugins-good |
34 | +Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, ubiquity (= ${binary:Version}), python-dbus, gir1.2-gtk-3.0, gir1.2-gdkpixbuf-2.0, gir1.2-gstreamer-0.10, gir1.2-soup-2.4, gir1.2-vte-2.90, gir1.2-webkit-3.0, iso-codes, x-window-manager, gksu, python-xklavier, gir1.2-timezonemap-1.0, python-libxml2, python-cairo, python-appindicator, gstreamer0.10-plugins-good |
35 | Suggests: gnome-control-center |
36 | Conflicts: ubuntu-express-frontend-gtk, espresso-frontend-gtk, ubiquity (<< 2.4.3) |
37 | Replaces: ubuntu-express-frontend-gtk, espresso-frontend-gtk, ubiquity (<< 2.4.3) |
38 | |
39 | === modified file 'tests/test_timezone.py' |
40 | --- tests/test_timezone.py 2011-08-30 17:20:27 +0000 |
41 | +++ tests/test_timezone.py 2011-09-30 21:34:24 +0000 |
42 | @@ -19,10 +19,26 @@ |
43 | controller.dbfilter = self.ubi_timezone.Page(None, db=db) |
44 | self.gtk = self.ubi_timezone.PageGtk(controller) |
45 | |
46 | + @mock.patch('gi.repository.Soup.SessionAsync') |
47 | + @mock.patch('gi.repository.Soup.Message') |
48 | @mock.patch('json.loads') |
49 | - @mock.patch('urllib2.build_opener') |
50 | - def test_city_entry(self, opener_mock, json_mock): |
51 | + def test_city_entry(self, json_mock, *args): |
52 | + from gi.repository import GObject |
53 | + |
54 | + # Patch GObject.timeout_add_seconds to call the supplied function |
55 | + # immediately rather than waiting for the interval to expire. |
56 | + def side_effect_factory(real_method): |
57 | + def side_effect(interval, function, data): |
58 | + function(data) |
59 | + return side_effect |
60 | + |
61 | json_mock.return_value = [] |
62 | + real_method = GObject.timeout_add_seconds |
63 | + method = mock.patch('gi.repository.GObject.timeout_add_seconds') |
64 | + timeout_mock = method.start() |
65 | + timeout_mock.side_effect = side_effect_factory(real_method) |
66 | + self.addCleanup(method.stop) |
67 | + |
68 | self.gtk.set_timezone('America/New_York') |
69 | self.gtk.city_entry.set_text('Eastern') |
70 | self.gtk.changed(self.gtk.city_entry) |
71 | |
72 | === modified file 'ubiquity/plugins/ubi-timezone.py' |
73 | --- ubiquity/plugins/ubi-timezone.py 2011-08-30 17:20:27 +0000 |
74 | +++ ubiquity/plugins/ubi-timezone.py 2011-09-30 21:34:24 +0000 |
75 | @@ -52,6 +52,9 @@ |
76 | self.timezone = None |
77 | self.zones = [] |
78 | self.plugin_widgets = self.page |
79 | + self.geoname_cache = {} |
80 | + self.geoname_session = None |
81 | + self.geoname_timeout_id = None |
82 | |
83 | def plugin_translate(self, lang): |
84 | #c = self.controller |
85 | @@ -84,54 +87,95 @@ |
86 | |
87 | |
88 | def changed(self, entry): |
89 | - from gi.repository import Gtk, GObject |
90 | + import urllib |
91 | + from gi.repository import Gtk, GObject, Soup |
92 | + |
93 | text = self.city_entry.get_text().decode('utf-8') |
94 | if not text: |
95 | return |
96 | # TODO if the completion widget has a selection, return? How do we |
97 | # determine this? |
98 | - if text in self.changed.cache: |
99 | - model = self.changed.cache[text] |
100 | + if text in self.geoname_cache: |
101 | + model = self.geoname_cache[text] |
102 | + self.city_entry.get_completion().set_model(model) |
103 | else: |
104 | - # fetch |
105 | model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING, |
106 | GObject.TYPE_STRING, GObject.TYPE_STRING, |
107 | GObject.TYPE_STRING) |
108 | - self.changed.cache[text] = model |
109 | - # TODO benchmark this |
110 | - results = [(name, self.tzdb.get_loc(city)) |
111 | - for (name, city) in |
112 | - [(x[0], x[1]) for x in self.zones |
113 | - if x[0].lower().split('(', 1)[-1] \ |
114 | - .startswith(text.lower())]] |
115 | - for result in results: |
116 | - # We use name rather than loc.human_zone for i18n. |
117 | - # TODO this looks pretty awful for US results: |
118 | - # United States (New York) (United States) |
119 | - # Might want to match the debconf format. |
120 | - name, loc = result |
121 | - model.append([name, '', loc.human_country, |
122 | - str(loc.latitude), str(loc.longitude)]) |
123 | - |
124 | - try: |
125 | - import urllib2, urllib, json |
126 | - opener = urllib2.build_opener() |
127 | - opener.addheaders = [('User-agent', 'Ubiquity/1.0')] |
128 | - url = opener.open(_geoname_url % |
129 | - (urllib.quote(text), misc.get_release().version)) |
130 | - for result in json.loads(url.read()): |
131 | - model.append([result['name'], |
132 | - result['admin1'], |
133 | - result['country'], |
134 | - result['latitude'], |
135 | - result['longitude']]) |
136 | - except Exception, e: |
137 | - print 'exception:', e |
138 | - # TODO because we don't return here, we could cache a |
139 | - # result that doesn't include the geonames results because |
140 | - # of a network error. |
141 | - self.city_entry.get_completion().set_model(model) |
142 | - changed.cache = {} |
143 | + |
144 | + if self.geoname_session is None: |
145 | + self.geoname_session = Soup.SessionAsync() |
146 | + url = _geoname_url % (urllib.quote(text.encode('UTF-8')), |
147 | + misc.get_release().version) |
148 | + message = Soup.Message.new('GET', url) |
149 | + message.request_headers.append('User-agent', 'Ubiquity/1.0') |
150 | + self.geoname_session.abort() |
151 | + if self.geoname_timeout_id is not None: |
152 | + GObject.source_remove(self.geoname_timeout_id) |
153 | + self.geoname_timeout_id = \ |
154 | + GObject.timeout_add_seconds(2, self.geoname_timeout, |
155 | + (text, model)) |
156 | + self.geoname_session.queue_message(message, self.geoname_cb, |
157 | + (text, model)) |
158 | + |
159 | + def geoname_add_tzdb(self, text, model): |
160 | + if len(model): |
161 | + # already added |
162 | + return |
163 | + |
164 | + # TODO benchmark this |
165 | + results = [(name, self.tzdb.get_loc(city)) |
166 | + for (name, city) in |
167 | + [(x[0], x[1]) for x in self.zones |
168 | + if x[0].lower().split('(', 1)[-1] \ |
169 | + .startswith(text.lower())]] |
170 | + for result in results: |
171 | + # We use name rather than loc.human_zone for i18n. |
172 | + # TODO this looks pretty awful for US results: |
173 | + # United States (New York) (United States) |
174 | + # Might want to match the debconf format. |
175 | + name, loc = result |
176 | + model.append([name, '', loc.human_country, |
177 | + str(loc.latitude), str(loc.longitude)]) |
178 | + |
179 | + def geoname_timeout(self, user_data): |
180 | + text, model = user_data |
181 | + self.geoname_add_tzdb(text, model) |
182 | + self.geoname_timeout_id = None |
183 | + self.city_entry.get_completion().set_model(model) |
184 | + return False |
185 | + |
186 | + def geoname_cb(self, session, message, user_data): |
187 | + import syslog |
188 | + import json |
189 | + from gi.repository import GObject, Soup |
190 | + |
191 | + text, model = user_data |
192 | + |
193 | + if self.geoname_timeout_id is not None: |
194 | + GObject.source_remove(self.geoname_timeout_id) |
195 | + self.geoname_timeout_id = None |
196 | + self.geoname_add_tzdb(text, model) |
197 | + |
198 | + if message.status_code == Soup.KnownStatusCode.CANCELLED: |
199 | + # Silently ignore cancellation. |
200 | + pass |
201 | + elif message.status_code != Soup.KnownStatusCode.OK: |
202 | + # Log but otherwise ignore failures. |
203 | + syslog.syslog('Geoname lookup for "%s" failed: %d %s' % |
204 | + (text, message.status_code, message.reason_phrase)) |
205 | + else: |
206 | + for result in json.loads(message.response_body.data): |
207 | + model.append([result['name'], |
208 | + result['admin1'], |
209 | + result['country'], |
210 | + result['latitude'], |
211 | + result['longitude']]) |
212 | + |
213 | + # Only cache positive results. |
214 | + self.geoname_cache[text] = model |
215 | + |
216 | + self.city_entry.get_completion().set_model(model) |
217 | |
218 | def setup_page(self): |
219 | # TODO Put a frame around the completion to add contrast (LP: #605908) |
./tests/ run-pyflakes plugins/ ubi-timezone. py:116: undefined name 'Gtk' plugins/ ubi-timezone. py:116: undefined name 'GObject' plugins/ ubi-timezone. py:116: undefined name 'GObject' plugins/ ubi-timezone. py:117: undefined name 'GObject' plugins/ ubi-timezone. py:117: undefined name 'GObject' plugins/ ubi-timezone. py:118: undefined name 'GObject'
ubiquity/
ubiquity/
ubiquity/
ubiquity/
ubiquity/
ubiquity/